Added schema level support for publication.
Hi,
This feature adds schema option while creating publication. Users will
be able to specify one or more schemas while creating publication,
when the user specifies schema option, then the data changes for the
tables present in the schema specified by the user will be replicated
to the subscriber. Few examples have been listed below:
Create a publication that publishes all changes for all the tables
present in production schema:
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA production;
Create a publication that publishes all changes for all the tables
present in marketing and sales schemas:
CREATE PUBLICATION sales_publication FOR ALL TABLES SCHEMA marketing, sales;
Add some schemas to the publication:
ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
Drop some schema from the publication:
ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
Attached is a POC patch for the same. I felt this feature would be quite useful.
Thoughts?
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v1-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Added-schema-level-support-for-publication.patchDownload
From f9b5134182229f718bbc1a9162b6043f879a6410 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 7 Jan 2021 11:38:17 +0530
Subject: [PATCH v1] Added schema level support for publication.
This patch adds schema level support for publication along with for all tables.
User can specify multiple schemas with schema option. When user specifies
schema option, then the tables present in the schema specified will be selected
by publisher for sending the data to subscriber.
---
doc/src/sgml/ref/alter_publication.sgml | 32 ++++++
doc/src/sgml/ref/create_publication.sgml | 33 +++++-
src/backend/catalog/pg_publication.c | 55 +++++++++-
src/backend/commands/publicationcmds.c | 172 +++++++++++++++++++++++++++++-
src/backend/parser/gram.y | 38 ++++++-
src/bin/pg_dump/pg_dump.c | 22 +++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 6 +-
src/include/catalog/pg_publication.h | 9 +-
src/include/nodes/parsenodes.h | 2 +
src/test/regress/expected/publication.out | 36 +++----
11 files changed, 376 insertions(+), 30 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index c2946df..c00b8da 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -97,6 +100,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</varlistentry>
<varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
<para>
@@ -141,6 +153,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
+</programlisting></para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbc..c941643 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,8 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR ALL TABLES [ SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ... ] ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -100,6 +100,19 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Specifies the list of schema that should be added to the publication.
+ If <literal>SCHEMA</literal> is specified, then the tables present in the
+ specified schema list is selected and added to the publication. The rest
+ of the tables will be skipped. This option should be specified
+ with <literal>FOR ALL TABLES</literal> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
<para>
@@ -222,6 +235,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+production schema:
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES schema production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+marketing and sales schemas:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES schema marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5f8e1c6..9264c9d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -349,13 +349,14 @@ GetAllTablesPublications(void)
* root partitioned tables.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(Publication *publication)
{
Relation classRel;
ScanKeyData key[1];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ bool pubviaroot = publication->pubviaroot;
classRel = table_open(RelationRelationId, AccessShareLock);
@@ -371,6 +372,16 @@ GetAllTablesPublicationRelations(bool pubviaroot)
Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
Oid relid = relForm->oid;
+ /*
+ * If schema is specified by the user, check if the relation is present
+ * in one of the schema specified.
+ */
+ if (publication->pubschemalist)
+ {
+ if (!list_member_oid(publication->pubschemalist, relForm->relnamespace))
+ continue;
+ }
+
if (is_publishable_class(relid, relForm) &&
!(relForm->relispartition && pubviaroot))
result = lappend_oid(result, relid);
@@ -405,6 +416,35 @@ GetAllTablesPublicationRelations(bool pubviaroot)
}
/*
+ * TextarrayToSchemaOidList
+ *
+ * Create schema oid List using schema Text Array.
+ */
+static List *
+TextarrayToSchemaOidList(ArrayType *arr)
+{
+ Datum *elems;
+ bool *nulls;
+ int nelems;
+ List *list = NIL;
+ int i;
+
+ deconstruct_array(arr, TEXTOID, -1, false, TYPALIGN_INT,
+ &elems, &nulls, &nelems);
+
+ for (i = 0; i < nelems; i++)
+ {
+ if (nulls[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("name or argument lists may not contain nulls")));
+ list = lappend_oid(list, get_namespace_oid(TextDatumGetCString(elems[i]), false));
+ }
+
+ return list;
+}
+
+/*
* Get publication using oid
*
* The Publication struct and its data are palloc'ed here.
@@ -415,6 +455,8 @@ GetPublication(Oid pubid)
HeapTuple tup;
Publication *pub;
Form_pg_publication pubform;
+ Datum datum;
+ bool isnull;
tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
if (!HeapTupleIsValid(tup))
@@ -432,6 +474,15 @@ GetPublication(Oid pubid)
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ datum = SysCacheGetAttr(PUBLICATIONOID,
+ tup,
+ Anum_pg_publication_pubschema,
+ &isnull);
+ if (!isnull)
+ pub->pubschemalist = TextarrayToSchemaOidList(DatumGetArrayTypeP(datum));
+ else
+ pub->pubschemalist = NIL;
+
ReleaseSysCache(tup);
return pub;
@@ -531,7 +582,7 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ tables = GetAllTablesPublicationRelations(publication);
else
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c..ceb7c4c 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -142,6 +142,29 @@ parse_publication_options(List *options,
}
/*
+ * Create a text array of schemas using schema list.
+ */
+static Datum
+SchemaListToArray(List *schemalist)
+{
+ ArrayType *arr;
+ Datum *datums;
+ int j = 0;
+ ListCell *cell;
+
+ datums = (Datum *) palloc(sizeof(Datum) * list_length(schemalist));
+ foreach(cell, schemalist)
+ {
+ char *name = strVal(lfirst(cell));
+ datums[j++] = CStringGetTextDatum(name);
+ }
+
+ arr = construct_array(datums, list_length(schemalist),
+ TEXTOID, -1, false, TYPALIGN_INT);
+ return PointerGetDatum(arr);
+}
+
+/*
* Create new publication.
*/
ObjectAddress
@@ -171,6 +194,10 @@ CreatePublication(CreatePublicationStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create FOR ALL TABLES publication")));
+ if (stmt->schemas && !stmt->for_all_tables)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("schema option cannot be used without specifying FOR ALL TABLES option")));
rel = table_open(PublicationRelationId, RowExclusiveLock);
/* Check if name is used */
@@ -213,6 +240,23 @@ CreatePublication(CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas && list_length(stmt->schemas))
+ {
+ ListCell *cell;
+ foreach(cell, stmt->schemas)
+ {
+ char *schema = strVal(lfirst(cell));
+
+ /* Check if the schema specified by the user exists. */
+ get_namespace_oid(schema, false);
+ }
+
+ values[Anum_pg_publication_pubschema - 1] =
+ SchemaListToArray(stmt->schemas);
+ }
+ else
+ nulls[Anum_pg_publication_pubschema - 1] = true;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -428,10 +472,132 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
}
/*
+ * Create a schema list using text array of schemas.
+ */
+static List *
+TextArrayToSchemaList(ArrayType *arr)
+{
+ Datum *elems;
+ bool *nulls;
+ int nelems;
+ List *list = NIL;
+ int i;
+
+ deconstruct_array(arr, TEXTOID, -1, false, TYPALIGN_INT,
+ &elems, &nulls, &nelems);
+
+ for (i = 0; i < nelems; i++)
+ {
+ if (nulls[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("name or argument lists may not contain nulls")));
+ list = lappend(list, makeString(TextDatumGetCString(elems[i])));
+ }
+
+ return list;
+}
+
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *pubschemalist = NIL;
+ ListCell *cell;
+ bool isnull;
+ Datum *values;
+ bool *nulls;
+ bool *replaces;
+ HeapTuple newtup;
+
+ Datum datum = SysCacheGetAttr(PUBLICATIONNAME, tup,
+ Anum_pg_publication_pubschema, &isnull);
+
+ if (!isnull)
+ pubschemalist = TextArrayToSchemaList(DatumGetArrayTypeP(datum));
+
+ values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum));
+ nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool));
+ replaces = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool));
+
+ /* Check if the schema specified by the user exists. */
+ if (stmt->tableAction == DEFELEM_ADD || stmt->tableAction == DEFELEM_SET)
+ {
+ foreach(cell, stmt->schemas)
+ get_namespace_oid(strVal(lfirst(cell)), false);
+ }
+
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ foreach(cell, stmt->schemas)
+ {
+ char *schema = strVal(lfirst(cell));
+
+ /*
+ * Check if the specified schema exists in the publisher schema
+ * list.
+ */
+ if (!list_member(pubschemalist, makeString(schema)))
+ pubschemalist = lappend(pubschemalist, makeString(schema));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("schema \"%s\" already exist in the publisher schema list", schema)));
+ }
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ {
+ foreach(cell, stmt->schemas)
+ {
+ char *schema = strVal(lfirst(cell));
+
+ /*
+ * Check if the specified schema exists in the publisher schema
+ * list.
+ */
+ if (list_member(pubschemalist, makeString(schema)))
+ pubschemalist = list_delete(pubschemalist, makeString(schema));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("schema \"%s\" does not exist in the publisher schema list", schema)));
+ }
+ }
+ else
+ pubschemalist = stmt->schemas;
+
+ if (pubschemalist && list_length(pubschemalist))
+ values[Anum_pg_publication_pubschema - 1] =
+ SchemaListToArray(pubschemalist);
+ else
+ nulls[Anum_pg_publication_pubschema - 1] = true;
+
+ replaces[Anum_pg_publication_pubschema - 1] = true;
+ newtup = heap_modify_tuple(tup, RelationGetDescr(rel),
+ values, nulls, replaces);
+ CatalogTupleUpdate(rel, &tup->t_self, newtup);
+
+ InvokeObjectPostAlterHook(PublicationRelationId, pubform->oid, 0);
+
+ /* Release memory */
+ pfree(values);
+ pfree(nulls);
+ pfree(replaces);
+ heap_freetuple(newtup);
+ return;
+}
+
+/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +626,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c9544..90d554e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -420,7 +420,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
+%type <node> opt_publication_for_tables publication_for_tables opt_schema_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -9462,6 +9462,10 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
}
;
+opt_schema_for_tables:
+ SCHEMA name_list { $$ = (Node *) $2; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
/*****************************************************************************
*
@@ -9470,7 +9474,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_publication_for_tables opt_definition opt_schema_for_tables
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
@@ -9484,6 +9488,7 @@ CreatePublicationStmt:
else
n->for_all_tables = true;
}
+ n->schemas = (List *)$6;
$$ = (Node *)n;
}
;
@@ -9515,6 +9520,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9549,6 +9559,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA name_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA name_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA name_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1f70653..bf8d534 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3881,6 +3881,7 @@ getPublications(Archive *fout)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubschema;
int i,
ntups;
@@ -3892,7 +3893,14 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot, array_to_string(p.pubschema, ',') AS pubschema "
+ "FROM pg_publication p",
+ username_subquery);
+ else if (fout->remoteVersion >= 130000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
@@ -3928,6 +3936,7 @@ getPublications(Archive *fout)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubschema = PQfnumber(res, "pubschema");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -3952,6 +3961,10 @@ getPublications(Archive *fout)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ if (!PQgetisnull(res, i, i_pubschema))
+ pubinfo[i].pubschema = pg_strdup(PQgetvalue(res, i, i_pubschema));
+ else
+ pubinfo[i].pubschema = NULL;
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4033,7 +4046,12 @@ dumpPublication(Archive *fout, PublicationInfo *pubinfo)
if (pubinfo->pubviaroot)
appendPQExpBufferStr(query, ", publish_via_partition_root = true");
- appendPQExpBufferStr(query, ");\n");
+ appendPQExpBufferStr(query, ")");
+
+ if (pubinfo->pubschema)
+ appendPQExpBuffer(query, " SCHEMA %s", pubinfo->pubschema);
+
+ appendPQExpBufferStr(query, ";\n");
ArchiveEntry(fout, pubinfo->dobj.catId, pubinfo->dobj.dumpId,
ARCHIVE_OPTS(.tag = pubinfo->dobj.name,
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index b3ce4ee..c02b912 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -606,6 +606,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char *pubschema;
} PublicationInfo;
/*
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index caf9756..0ab1a61 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5746,7 +5746,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};
if (pset.sversion < 100000)
{
@@ -5781,6 +5781,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n pubschema AS \"%s\"",
+ gettext_noop("Schemas"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 0dd50fe..7c93148 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -54,6 +54,10 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text pubschema[1] BKI_FORCE_NULL; /* schema names */
+#endif
} FormData_pg_publication;
/* ----------------
@@ -63,6 +67,8 @@ CATALOG(pg_publication,6104,PublicationRelationId)
*/
typedef FormData_pg_publication *Form_pg_publication;
+DECLARE_TOAST(pg_publication, 13734, 13735);
+
DECLARE_UNIQUE_INDEX(pg_publication_oid_index, 6110, on pg_publication using btree(oid oid_ops));
#define PublicationObjectIndexId 6110
DECLARE_UNIQUE_INDEX(pg_publication_pubname_index, 6111, on pg_publication using btree(pubname name_ops));
@@ -83,6 +89,7 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ List *pubschemalist;
} Publication;
extern Publication *GetPublication(Oid pubid);
@@ -107,7 +114,7 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetAllTablesPublicationRelations(Publication *publication);
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index dc2bb40..0697533 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3523,6 +3523,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Options list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3537,6 +3538,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Options list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7..420c8a2 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | Schemas
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f |
+ testpub_default | regress_publication_user | f | f | t | f | f | f |
(2 rows)
ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete');
\dRp
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f
- testpub_default | regress_publication_user | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Schemas
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f |
+ testpub_default | regress_publication_user | f | t | t | t | f | f |
(2 rows)
--- adding tables
@@ -271,20 +271,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 | Truncates | Via root | Schemas
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f |
(1 row)
-- rename back to keep the rest simple
ALTER PUBLICATION testpub_foo RENAME TO testpub_default;
ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
------------------+---------------------------+------------+---------+---------+---------+-----------+----------
- testpub_default | regress_publication_user2 | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Schemas
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f |
(1 row)
DROP PUBLICATION testpub_default;
--
1.8.3.1
On Thu, Jan 7, 2021 at 10:03 PM vignesh C <vignesh21@gmail.com> wrote:
This feature adds schema option while creating publication. Users will
be able to specify one or more schemas while creating publication,
when the user specifies schema option, then the data changes for the
tables present in the schema specified by the user will be replicated
to the subscriber. Few examples have been listed below:Create a publication that publishes all changes for all the tables
present in production schema:
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA production;Create a publication that publishes all changes for all the tables
present in marketing and sales schemas:
CREATE PUBLICATION sales_publication FOR ALL TABLES SCHEMA marketing, sales;Add some schemas to the publication:
ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;Drop some schema from the publication:
ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;Attached is a POC patch for the same. I felt this feature would be quite useful.
What do we do if the user Drops the schema? Do we automatically remove
it from the publication?
I see some use of such a feature but you haven't described the use
case or how did you arrive at the conclusion that it would be quite
useful?
--
With Regards,
Amit Kapila.
On Fri, Jan 8, 2021 at 4:32 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Jan 7, 2021 at 10:03 PM vignesh C <vignesh21@gmail.com> wrote:
This feature adds schema option while creating publication. Users will
be able to specify one or more schemas while creating publication,
when the user specifies schema option, then the data changes for the
tables present in the schema specified by the user will be replicated
to the subscriber. Few examples have been listed below:Create a publication that publishes all changes for all the tables
present in production schema:
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA production;Create a publication that publishes all changes for all the tables
present in marketing and sales schemas:
CREATE PUBLICATION sales_publication FOR ALL TABLES SCHEMA marketing, sales;Add some schemas to the publication:
ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;Drop some schema from the publication:
ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;Attached is a POC patch for the same. I felt this feature would be quite useful.
What do we do if the user Drops the schema? Do we automatically remove
it from the publication?
I have not yet handled this scenario yet, I will handle this and
adding of tests in the next patch.
I see some use of such a feature but you haven't described the use
case or how did you arrive at the conclusion that it would be quite
useful?
Currently there are a couple of options "FOR All TABLES" and "FOR
TABLE" when a user creates a publication, 1) either to subscribe to
the changes of all the tables or 2) subscribe to a few tables. There
is no option for users to subscribe to relations present in the
schemas. User has to manually identify the list of tables present in
the schema and specify the list of tables in that schema using the
"FOR TABLE" option. Similarly if a user wants to subscribe to n number
of schemas, the user has to do this for the required schemas, this is
a tedious process. This feature helps the user to take care of this
internally using schema option.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Sat, Jan 9, 2021 at 5:21 PM vignesh C <vignesh21@gmail.com> wrote:
What do we do if the user Drops the schema? Do we automatically remove
it from the publication?I have not yet handled this scenario yet, I will handle this and
adding of tests in the next patch.I see some use of such a feature but you haven't described the use
case or how did you arrive at the conclusion that it would be quite
useful?Currently there are a couple of options "FOR All TABLES" and "FOR
TABLE" when a user creates a publication, 1) either to subscribe to
the changes of all the tables or 2) subscribe to a few tables. There
is no option for users to subscribe to relations present in the
schemas. User has to manually identify the list of tables present in
the schema and specify the list of tables in that schema using the
"FOR TABLE" option. Similarly if a user wants to subscribe to n number
of schemas, the user has to do this for the required schemas, this is
a tedious process. This feature helps the user to take care of this
internally using schema option.
I think this feature can be useful, in case a user has a lot of tables
to publish inside a schema. Having said that, I wonder if this feature
mandates users to create the same schema with same
permissions/authorizations manually on the subscriber, because logical
replication doesn't propagate any ddl's so are the schema or schema
changes? Or is it that the list of tables from the publisher can go
into a different schema on the subscriber?
Since the schema can have other objects such as data types, functions,
operators, I'm sure with your feature, non-table objects will be
skipped.
As Amit pointed out earlier, the behaviour when schema dropped, I
think we should also consider when schema is altered, say altered to a
different name, maybe we should change that in the publication too.
In general, what happens if we have some temporary tables or foreign
tables inside the schema, will they be allowed to send the data to
subscribers?
And, with this feature, since there can be many huge tables inside a
schema, the initial table sync phase of the replication can take a
while.
Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?
IMO, it's better to have the syntax - CREATE PUBLICATION
production_publication FOR ALL TABLES IN SCHEMA production - just
added IN between for all tables and schema.
Say a user has a schema with 121 tables in it, and wants to replicate
only 120 or 199 or even lesser tables out of it, so can we have some
skip option to the new syntax, something like below?
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA
production WITH skip = marketing, accounts, sales; --> meaning is,
replicate all the tables in the schema production except marketing,
accounts, sales tables.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Sat, Jan 9, 2021 at 8:08 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
I think this feature can be useful, in case a user has a lot of tables
to publish inside a schema. Having said that, I wonder if this feature
mandates users to create the same schema with same
permissions/authorizations manually on the subscriber, because logical
replication doesn't propagate any ddl's so are the schema or schema
changes? Or is it that the list of tables from the publisher can go
into a different schema on the subscriber?Since the schema can have other objects such as data types, functions,
operators, I'm sure with your feature, non-table objects will be
skipped.As Amit pointed out earlier, the behaviour when schema dropped, I
think we should also consider when schema is altered, say altered to a
different name, maybe we should change that in the publication too.In general, what happens if we have some temporary tables or foreign
tables inside the schema, will they be allowed to send the data to
subscribers?And, with this feature, since there can be many huge tables inside a
schema, the initial table sync phase of the replication can take a
while.Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?IMO, it's better to have the syntax - CREATE PUBLICATION
production_publication FOR ALL TABLES IN SCHEMA production - just
added IN between for all tables and schema.Say a user has a schema with 121 tables in it, and wants to replicate
only 120 or 199 or even lesser tables out of it, so can we have some
skip option to the new syntax, something like below?
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA
production WITH skip = marketing, accounts, sales; --> meaning is,
replicate all the tables in the schema production except marketing,
accounts, sales tables.
One more point - if the publication is created for a schema with no or
some initial tables, will all the future tables that may get added to
the schema will be replicated too?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Sat, Jan 9, 2021 at 8:14 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Sat, Jan 9, 2021 at 8:08 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I think this feature can be useful, in case a user has a lot of tables
to publish inside a schema. Having said that, I wonder if this feature
mandates users to create the same schema with same
permissions/authorizations manually on the subscriber, because logical
replication doesn't propagate any ddl's so are the schema or schema
changes? Or is it that the list of tables from the publisher can go
into a different schema on the subscriber?Since the schema can have other objects such as data types, functions,
operators, I'm sure with your feature, non-table objects will be
skipped.As Amit pointed out earlier, the behaviour when schema dropped, I
think we should also consider when schema is altered, say altered to a
different name, maybe we should change that in the publication too.In general, what happens if we have some temporary tables or foreign
tables inside the schema, will they be allowed to send the data to
subscribers?And, with this feature, since there can be many huge tables inside a
schema, the initial table sync phase of the replication can take a
while.Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?IMO, it's better to have the syntax - CREATE PUBLICATION
production_publication FOR ALL TABLES IN SCHEMA production - just
added IN between for all tables and schema.Say a user has a schema with 121 tables in it, and wants to replicate
only 120 or 199 or even lesser tables out of it, so can we have some
skip option to the new syntax, something like below?
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA
production WITH skip = marketing, accounts, sales; --> meaning is,
replicate all the tables in the schema production except marketing,
accounts, sales tables.One more point - if the publication is created for a schema with no or
some initial tables, will all the future tables that may get added to
the schema will be replicated too?
I expect this should be the behavior.
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
Thanks for your comments Bharath, please find my opinion below.
On Sat, Jan 9, 2021 at 8:08 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
I think this feature can be useful, in case a user has a lot of tables
to publish inside a schema. Having said that, I wonder if this feature
mandates users to create the same schema with same
permissions/authorizations manually on the subscriber, because logical
replication doesn't propagate any ddl's so are the schema or schema
changes? Or is it that the list of tables from the publisher can go
into a different schema on the subscriber?
DDL's will not be propagated to the subscriber. Users have to create
the schema & tables in the subscriber. No change in
Permissions/authorizations handling, it will be the same as the
existing behavior for relations.
Since the schema can have other objects such as data types, functions,
operators, I'm sure with your feature, non-table objects will be
skipped.
Yes, only table data will be sent to subscribers, non-table objects
will be skipped.
As Amit pointed out earlier, the behaviour when schema dropped, I
think we should also consider when schema is altered, say altered to a
different name, maybe we should change that in the publication too.
I agree that when schema is altered the renamed schema should be
reflected in the publication.
In general, what happens if we have some temporary tables or foreign
tables inside the schema, will they be allowed to send the data to
subscribers?
Temporary tables & foreign tables will not be added to the publications.
And, with this feature, since there can be many huge tables inside a
schema, the initial table sync phase of the replication can take a
while.
Yes this is required.
Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?
There is no provision for this currently.
IMO, it's better to have the syntax - CREATE PUBLICATION
production_publication FOR ALL TABLES IN SCHEMA production - just
added IN between for all tables and schema.
I'm ok with the proposed syntax, I would like others' opinion too
before making the change.
Say a user has a schema with 121 tables in it, and wants to replicate
only 120 or 199 or even lesser tables out of it, so can we have some
skip option to the new syntax, something like below?
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA
production WITH skip = marketing, accounts, sales; --> meaning is,
replicate all the tables in the schema production except marketing,
accounts, sales tables.
Yes this is a good use case, will include this change.
Thanks for the comments, I will handle the comments and post a patch for this.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Sat, Jan 9, 2021 at 8:14 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
One more point - if the publication is created for a schema with no or
some initial tables, will all the future tables that may get added to
the schema will be replicated too?
I agree on this, when a relation is added to the schema it should be
added to the publication.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Sun, Jan 10, 2021 at 11:21 PM vignesh C <vignesh21@gmail.com> wrote:
On Sat, Jan 9, 2021 at 8:08 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I think this feature can be useful, in case a user has a lot of tables
to publish inside a schema. Having said that, I wonder if this feature
mandates users to create the same schema with same
permissions/authorizations manually on the subscriber, because logical
replication doesn't propagate any ddl's so are the schema or schema
changes? Or is it that the list of tables from the publisher can go
into a different schema on the subscriber?DDL's will not be propagated to the subscriber. Users have to create
the schema & tables in the subscriber. No change in
Permissions/authorizations handling, it will be the same as the
existing behavior for relations.
Looks like the existing behaviour already requires users to create the
schema on the subscriber when publishing the tables from that schema.
Otherwise, an error is thrown on the subscriber [1]https://www.postgresql.org/docs/devel/sql-alterpublication.html.
[1]: https://www.postgresql.org/docs/devel/sql-alterpublication.html
CREATE SCHEMA myschema;
CREATE TABLE myschema.t1(a1 int, b1 int);
INSERT INTO myschema.t1_myschema SELECT i, i+10 FROM generate_series(1,10) i;
CREATE PUBLICATION testpub FOR TABLE myschema.t1;
on subscriber:
postgres=# CREATE SUBSCRIPTION testsub CONNECTION 'host=localhost
dbname=postgres user=bharath port=5432' PUBLICATION testpub;
ERROR: schema "myschema" does not exist
CREATE SCHEMA myschema;
CREATE TABLE myschema.t1(a1 int, b1 int);
postgres=# CREATE SUBSCRIPTION testsub CONNECTION 'host=localhost
dbname=postgres user=bharath port=5432' PUBLICATION testpub;
NOTICE: created replication slot "testsub" on publisher
CREATE SUBSCRIPTION
Since the schema can have other objects such as data types, functions,
operators, I'm sure with your feature, non-table objects will be
skipped.Yes, only table data will be sent to subscribers, non-table objects
will be skipped.
Looks like the existing CREATE PUBLICATION FOR ALL TABLES, which is
for all the tables in the database, does this i.e. skips non-table
objects and temporary tables, foreign tables and so on. So, your
feature also can behave the same way, but within the scope of the
given schema/s.
As Amit pointed out earlier, the behaviour when schema dropped, I
think we should also consider when schema is altered, say altered to a
different name, maybe we should change that in the publication too.I agree that when schema is altered the renamed schema should be
reflected in the publication.
I think, it's not only making sure that the publisher side has the new
altered schema, but also the subscriber needs those alters. Having
said that, since these alters come under DDL changes and in logical
replication we don't publish the scheme changes to the subscriber, we
may not need to anything extra for informing the schema alters to the
subscriber from the publisher, the users might have to do the same
schema alter on the subscriber and then a ALTER SUBSCRIPTION testsub
REFRESH PUBLICATION; should work for them? If this understanding is
correct, then we should document this.
In general, what happens if we have some temporary tables or foreign
tables inside the schema, will they be allowed to send the data to
subscribers?Temporary tables & foreign tables will not be added to the publications.
Yes the existing logical replication framework doesn't allow
replication of temporary, unlogged, foreign tables and other non-table
relations such as materialized views, indexes etc [1]https://www.postgresql.org/docs/devel/sql-alterpublication.html. The CREATE
PUBLICATION statement either fails in check_publication_add_relation
or before that.
CREATE PUBLICATION testpub FOR TABLE tab1, throwing the error if the
single table tab1 is any of the above restricted tables, seems fine.
But, if there's a list of tables with CREATE PUBLICATION testpub FOR
TABLE normal_tab1, temp_tab2, normal_tab3, foreign_tab4,
unlogged_tab5, normal_tab6, normal_tab7 ......; This query fails on
first encounter of the restricted table, say at temp_tab2. Whereas,
CREATE PUBLICATION testpub FOR ALL TABLES; would skip the restricted
tables and continue to add the accepted tables into the publication
within the database.
IMHO, if there's a list of tables specified with FOR TABLE, then
instead of throwing an error in case of any restricted table, we can
issue a warning and continue with the addition of accepted tables into
the publication. If done, this behaviour will be in sync with FOR ALL
TABLES;
Thoughts? If okay, I can work on a patch.
Related to error messages: when foreign table is specified in CREATE
PUBLICATION statement, then "ERROR: "f1" is not a table", is thrown
[1]: https://www.postgresql.org/docs/devel/sql-alterpublication.html
replicated". In general, it would be good if we could have the error
messages something like in [2]t1 is a temporary table: postgres=# CREATE PUBLICATION testpub FOR TABLE t1; ERROR: temporary table "t1" cannot be replicated DETAIL: Temporary, unlogged and foreign relations cannot be replicated. instead of the existing [1]https://www.postgresql.org/docs/devel/sql-alterpublication.html.
Thoughts? If okay, I can work on a patch.
[1]: https://www.postgresql.org/docs/devel/sql-alterpublication.html
t1 is a temporary table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: table "t1" cannot be replicated
DETAIL: Temporary and unlogged relations cannot be replicated.
t1 is an unlogged table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: table "t1" cannot be replicated
DETAIL: Temporary and unlogged relations cannot be replicated.
f1 is a foreign table:
postgres=# CREATE PUBLICATION testpub FOR TABLE f1;
ERROR: "f1" is not a table
DETAIL: Only tables can be added to publications.
mv1 is a mat view:
postgres=# CREATE PUBLICATION testpub FOR TABLE mv1;
ERROR: "mv1" is not a table
idx1 is an index:
postgres=# CREATE PUBLICATION testpub FOR TABLE idx1;
ERROR: "idx1" is an index
[2]: t1 is a temporary table: postgres=# CREATE PUBLICATION testpub FOR TABLE t1; ERROR: temporary table "t1" cannot be replicated DETAIL: Temporary, unlogged and foreign relations cannot be replicated.
t1 is a temporary table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: temporary table "t1" cannot be replicated
DETAIL: Temporary, unlogged and foreign relations cannot be replicated.
t1 is an unlogged table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: unlogged table "t1" cannot be replicated
DETAIL: Temporary, unlogged and foreign relations cannot be replicated.
f1 is a foreign table:
postgres=# CREATE PUBLICATION testpub FOR TABLE f1;
ERROR: foreign table "f1" cannot be replicated
DETAIL: Temporary, unlogged and foreign relations cannot be replicated.
Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?There is no provision for this currently.
The documentation [1]https://www.postgresql.org/docs/devel/sql-alterpublication.html says, we can ALTER PUBLICATION testpub DROP
TABLE t1; which removes the table from the list of published tables,
but looks like it requires ALTER SUBSCRIPTION testsub REFRESH
PUBLICATION; for the changes to become effective on the subscriber. I
have done some testing for this case:
1) created publication for table t1, see \d+ t1, the associated
publication is visible in the output
2) created subscription on the subscriber, initial available data from
the publisher for table t1 is received
3) insert into table t1 on the publisher
4) inserted data in (3) is received in the subscriber table t1
5) alter publication to drop the table t1 on the publisher, see \d+
t1, there will not be any associated publication in the output
6) execute alter subscription refresh publication on the subscriber,
with the expectation that it should not receive the data from the
publisher for the table t1 since it's dropped from the publication in
(5)
7) insert into table t1 on the publisher
8) still the newly inserted data in (7) from the publisher, will be
received into the table t1 in the subscriber
IIUC, the behaviour of ALTER PUBLICATION DROP TABLE from the docs and
the above use case, it looks like a bug to me. If I'm wrong, can
someone correct me?
Thoughts?
[1]: https://www.postgresql.org/docs/devel/sql-alterpublication.html
IMO, it's better to have the syntax - CREATE PUBLICATION
production_publication FOR ALL TABLES IN SCHEMA production - just
added IN between for all tables and schema.I'm ok with the proposed syntax, I would like others' opinion too
before making the change.
Thanks.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Mon, 11 Jan 2021 at 14:15, Bharath Rupireddy wrote:
On Sun, Jan 10, 2021 at 11:21 PM vignesh C <vignesh21@gmail.com> wrote:
On Sat, Jan 9, 2021 at 8:08 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I think this feature can be useful, in case a user has a lot of tables
to publish inside a schema. Having said that, I wonder if this feature
mandates users to create the same schema with same
permissions/authorizations manually on the subscriber, because logical
replication doesn't propagate any ddl's so are the schema or schema
changes? Or is it that the list of tables from the publisher can go
into a different schema on the subscriber?DDL's will not be propagated to the subscriber. Users have to create
the schema & tables in the subscriber. No change in
Permissions/authorizations handling, it will be the same as the
existing behavior for relations.Looks like the existing behaviour already requires users to create the
schema on the subscriber when publishing the tables from that schema.
Otherwise, an error is thrown on the subscriber [1].[1] on publisher:
CREATE SCHEMA myschema;
CREATE TABLE myschema.t1(a1 int, b1 int);
INSERT INTO myschema.t1_myschema SELECT i, i+10 FROM generate_series(1,10) i;
CREATE PUBLICATION testpub FOR TABLE myschema.t1;on subscriber:
postgres=# CREATE SUBSCRIPTION testsub CONNECTION 'host=localhost
dbname=postgres user=bharath port=5432' PUBLICATION testpub;
ERROR: schema "myschema" does not exist
CREATE SCHEMA myschema;
CREATE TABLE myschema.t1(a1 int, b1 int);
postgres=# CREATE SUBSCRIPTION testsub CONNECTION 'host=localhost
dbname=postgres user=bharath port=5432' PUBLICATION testpub;
NOTICE: created replication slot "testsub" on publisher
CREATE SUBSCRIPTIONSince the schema can have other objects such as data types, functions,
operators, I'm sure with your feature, non-table objects will be
skipped.Yes, only table data will be sent to subscribers, non-table objects
will be skipped.Looks like the existing CREATE PUBLICATION FOR ALL TABLES, which is
for all the tables in the database, does this i.e. skips non-table
objects and temporary tables, foreign tables and so on. So, your
feature also can behave the same way, but within the scope of the
given schema/s.As Amit pointed out earlier, the behaviour when schema dropped, I
think we should also consider when schema is altered, say altered to a
different name, maybe we should change that in the publication too.I agree that when schema is altered the renamed schema should be
reflected in the publication.I think, it's not only making sure that the publisher side has the new
altered schema, but also the subscriber needs those alters. Having
said that, since these alters come under DDL changes and in logical
replication we don't publish the scheme changes to the subscriber, we
may not need to anything extra for informing the schema alters to the
subscriber from the publisher, the users might have to do the same
schema alter on the subscriber and then a ALTER SUBSCRIPTION testsub
REFRESH PUBLICATION; should work for them? If this understanding is
correct, then we should document this.In general, what happens if we have some temporary tables or foreign
tables inside the schema, will they be allowed to send the data to
subscribers?Temporary tables & foreign tables will not be added to the publications.
Yes the existing logical replication framework doesn't allow
replication of temporary, unlogged, foreign tables and other non-table
relations such as materialized views, indexes etc [1]. The CREATE
PUBLICATION statement either fails in check_publication_add_relation
or before that.CREATE PUBLICATION testpub FOR TABLE tab1, throwing the error if the
single table tab1 is any of the above restricted tables, seems fine.
But, if there's a list of tables with CREATE PUBLICATION testpub FOR
TABLE normal_tab1, temp_tab2, normal_tab3, foreign_tab4,
unlogged_tab5, normal_tab6, normal_tab7 ......; This query fails on
first encounter of the restricted table, say at temp_tab2. Whereas,
CREATE PUBLICATION testpub FOR ALL TABLES; would skip the restricted
tables and continue to add the accepted tables into the publication
within the database.IMHO, if there's a list of tables specified with FOR TABLE, then
instead of throwing an error in case of any restricted table, we can
issue a warning and continue with the addition of accepted tables into
the publication. If done, this behaviour will be in sync with FOR ALL
TABLES;Thoughts? If okay, I can work on a patch.
Related to error messages: when foreign table is specified in CREATE
PUBLICATION statement, then "ERROR: "f1" is not a table", is thrown
[1], how about the error message "ERROR: foerign table "f1" cannot be
replicated". In general, it would be good if we could have the error
messages something like in [2] instead of the existing [1].Thoughts? If okay, I can work on a patch.
[1]
t1 is a temporary table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: table "t1" cannot be replicated
DETAIL: Temporary and unlogged relations cannot be replicated.t1 is an unlogged table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: table "t1" cannot be replicated
DETAIL: Temporary and unlogged relations cannot be replicated.f1 is a foreign table:
postgres=# CREATE PUBLICATION testpub FOR TABLE f1;
ERROR: "f1" is not a table
DETAIL: Only tables can be added to publications.mv1 is a mat view:
postgres=# CREATE PUBLICATION testpub FOR TABLE mv1;
ERROR: "mv1" is not a tableidx1 is an index:
postgres=# CREATE PUBLICATION testpub FOR TABLE idx1;
ERROR: "idx1" is an index[2]
t1 is a temporary table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: temporary table "t1" cannot be replicated
DETAIL: Temporary, unlogged and foreign relations cannot be replicated.t1 is an unlogged table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: unlogged table "t1" cannot be replicated
DETAIL: Temporary, unlogged and foreign relations cannot be replicated.f1 is a foreign table:
postgres=# CREATE PUBLICATION testpub FOR TABLE f1;
ERROR: foreign table "f1" cannot be replicated
DETAIL: Temporary, unlogged and foreign relations cannot be replicated.Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?There is no provision for this currently.
The documentation [1] says, we can ALTER PUBLICATION testpub DROP
TABLE t1; which removes the table from the list of published tables,
but looks like it requires ALTER SUBSCRIPTION testsub REFRESH
PUBLICATION; for the changes to become effective on the subscriber. I
have done some testing for this case:
1) created publication for table t1, see \d+ t1, the associated
publication is visible in the output
2) created subscription on the subscriber, initial available data from
the publisher for table t1 is received
3) insert into table t1 on the publisher
4) inserted data in (3) is received in the subscriber table t1
5) alter publication to drop the table t1 on the publisher, see \d+
t1, there will not be any associated publication in the output
6) execute alter subscription refresh publication on the subscriber,
with the expectation that it should not receive the data from the
publisher for the table t1 since it's dropped from the publication in
(5)
7) insert into table t1 on the publisher
8) still the newly inserted data in (7) from the publisher, will be
received into the table t1 in the subscriberIIUC, the behaviour of ALTER PUBLICATION DROP TABLE from the docs and
the above use case, it looks like a bug to me. If I'm wrong, can
someone correct me?
Yes, if we modify the publication, we should refresh the subscription on
each subscriber. It looks strange for me, especially for partitioned
tables [1]/messages/by-id/1D6DCFD2-0F44-4A18-BF67-17C2697B1631@hotmail.com.
Thoughts?
Can we trace the different between publication and subscription, and
auto-refresh subscription on subscriber?
[1]: /messages/by-id/1D6DCFD2-0F44-4A18-BF67-17C2697B1631@hotmail.com
/messages/by-id/1D6DCFD2-0F44-4A18-BF67-17C2697B1631@hotmail.com
--
Regrads,
Japin Li.
ChengDu WenWu Information Technology Co.,Ltd.
On Mon, Jan 11, 2021 at 1:29 PM japin <japinli@hotmail.com> wrote:
Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?There is no provision for this currently.
The documentation [1] says, we can ALTER PUBLICATION testpub DROP
TABLE t1; which removes the table from the list of published tables,
but looks like it requires ALTER SUBSCRIPTION testsub REFRESH
PUBLICATION; for the changes to become effective on the subscriber. I
have done some testing for this case:
1) created publication for table t1, see \d+ t1, the associated
publication is visible in the output
2) created subscription on the subscriber, initial available data from
the publisher for table t1 is received
3) insert into table t1 on the publisher
4) inserted data in (3) is received in the subscriber table t1
5) alter publication to drop the table t1 on the publisher, see \d+
t1, there will not be any associated publication in the output
6) execute alter subscription refresh publication on the subscriber,
with the expectation that it should not receive the data from the
publisher for the table t1 since it's dropped from the publication in
(5)
7) insert into table t1 on the publisher
8) still the newly inserted data in (7) from the publisher, will be
received into the table t1 in the subscriberIIUC, the behaviour of ALTER PUBLICATION DROP TABLE from the docs and
the above use case, it looks like a bug to me. If I'm wrong, can
someone correct me?Yes, if we modify the publication, we should refresh the subscription on
each subscriber. It looks strange for me, especially for partitioned
tables [1].Thoughts?
Can we trace the different between publication and subscription, and
auto-refresh subscription on subscriber?[1]
/messages/by-id/1D6DCFD2-0F44-4A18-BF67-17C2697B1631@hotmail.com
As Amit stated in your thread [1]/messages/by-id/CALj2ACWAxO3vSToT0o5nXL=rz5cNx90zaV-at=cvM14Tag4=cQ@mail.gmail.com, DDLs like creation of the new
tables or partitions, schema changes etc. on the publisher can not be
replicated automatically by the logical replication framework to the
subscriber. Users have to perform those DDLs on the subscribers by
themselves.
If your point is to at least issue the ALTER SUBSCRIPTION testsub
REFRESH PUBLICATION; from the publication whenever the publication is
altered i.e. added or dropped tables, IMO, we cannot do this, because
running this command on the subscriber only makes sense, after user
runs the same DDLs (which were run on the publisher) also on the
subscriber. To illustrate this:
1) create a new table or partition on the publisher and add it to
publisher, note that the same table has not yet been created on the
subscriber
2) imagine the publisher issuing an auto refresh command to all the
subscribers, then, no point in that right, because the new table or
the partition is not yet created on all the subscribers.
So, IMO, we can not have an auto refresh mechanism, until we have the
feature to replicate the DDL changes to all the subscribers.
What I stated in my earlier mail [1]/messages/by-id/CALj2ACWAxO3vSToT0o5nXL=rz5cNx90zaV-at=cvM14Tag4=cQ@mail.gmail.com is that even though we drop the
table from the publication in the publisher and run a refresh
publication on the subscriber, still the data is being replicated from
the publisher to the subscriber table. I just wanted to know whether
this is the expected behaviour or what exactly means. a user running
ALTER PUBLICATION mypub DROP TABLE mytable;
[1]: /messages/by-id/CALj2ACWAxO3vSToT0o5nXL=rz5cNx90zaV-at=cvM14Tag4=cQ@mail.gmail.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Jan 11, 2021, at 5:06 PM, Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com<mailto:bharath.rupireddyforpostgres@gmail.com>> wrote:
On Mon, Jan 11, 2021 at 1:29 PM japin <japinli@hotmail.com<mailto:japinli@hotmail.com>> wrote:
Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?
There is no provision for this currently.
The documentation [1]/messages/by-id/CALj2ACWAxO3vSToT0o5nXL=rz5cNx90zaV-at=cvM14Tag4=cQ@mail.gmail.com says, we can ALTER PUBLICATION testpub DROP
TABLE t1; which removes the table from the list of published tables,
but looks like it requires ALTER SUBSCRIPTION testsub REFRESH
PUBLICATION; for the changes to become effective on the subscriber. I
have done some testing for this case:
1) created publication for table t1, see \d+ t1, the associated
publication is visible in the output
2) created subscription on the subscriber, initial available data from
the publisher for table t1 is received
3) insert into table t1 on the publisher
4) inserted data in (3) is received in the subscriber table t1
5) alter publication to drop the table t1 on the publisher, see \d+
t1, there will not be any associated publication in the output
6) execute alter subscription refresh publication on the subscriber,
with the expectation that it should not receive the data from the
publisher for the table t1 since it's dropped from the publication in
(5)
7) insert into table t1 on the publisher
8) still the newly inserted data in (7) from the publisher, will be
received into the table t1 in the subscriber
IIUC, the behaviour of ALTER PUBLICATION DROP TABLE from the docs and
the above use case, it looks like a bug to me. If I'm wrong, can
someone correct me?
Yes, if we modify the publication, we should refresh the subscription on
each subscriber. It looks strange for me, especially for partitioned
tables [1]/messages/by-id/CALj2ACWAxO3vSToT0o5nXL=rz5cNx90zaV-at=cvM14Tag4=cQ@mail.gmail.com.
Thoughts?
Can we trace the different between publication and subscription, and
auto-refresh subscription on subscriber?
[1]: /messages/by-id/CALj2ACWAxO3vSToT0o5nXL=rz5cNx90zaV-at=cvM14Tag4=cQ@mail.gmail.com
/messages/by-id/1D6DCFD2-0F44-4A18-BF67-17C2697B1631@hotmail.com
As Amit stated in your thread [1]/messages/by-id/CALj2ACWAxO3vSToT0o5nXL=rz5cNx90zaV-at=cvM14Tag4=cQ@mail.gmail.com, DDLs like creation of the new
tables or partitions, schema changes etc. on the publisher can not be
replicated automatically by the logical replication framework to the
subscriber. Users have to perform those DDLs on the subscribers by
themselves.
Yeah, DDLs is not supported now. On publisher, the partitions are added to the
publication automatically. However, even if we created the partitions on subscriber,
it will not sync the new partitions, because it likes normal table, we must execute
ALTER SUBSCRIPTION my_test REFRESH PUBLICATION;
I preferred it will automatically add to subscription when we create the new partitions
if the partitions is already in publication.
If your point is to at least issue the ALTER SUBSCRIPTION testsub
REFRESH PUBLICATION; from the publication whenever the publication is
altered i.e. added or dropped tables, IMO, we cannot do this, because
running this command on the subscriber only makes sense, after user
runs the same DDLs (which were run on the publisher) also on the
subscriber. To illustrate this:
1) create a new table or partition on the publisher and add it to
publisher, note that the same table has not yet been created on the
subscriber
2) imagine the publisher issuing an auto refresh command to all the
subscribers, then, no point in that right, because the new table or
the partition is not yet created on all the subscribers.
So, IMO, we can not have an auto refresh mechanism, until we have the
feature to replicate the DDL changes to all the subscribers.
Thanks for clarification.
What I stated in my earlier mail [1]/messages/by-id/CALj2ACWAxO3vSToT0o5nXL=rz5cNx90zaV-at=cvM14Tag4=cQ@mail.gmail.com is that even though we drop the
table from the publication in the publisher and run a refresh
publication on the subscriber, still the data is being replicated from
the publisher to the subscriber table. I just wanted to know whether
this is the expected behaviour or what exactly means. a user running
ALTER PUBLICATION mypub DROP TABLE mytable;
[1]: /messages/by-id/CALj2ACWAxO3vSToT0o5nXL=rz5cNx90zaV-at=cvM14Tag4=cQ@mail.gmail.com
Sorry, I misunderstood. After the test (ce6a71fa530). I found that if we do not insert data
between step (5) and (6), it will not ship the new records, however, if we insert
data between step (5) and (6), it will ship the new records.
(1) created publication for table t1, t2
postgres[8765]=# CREATE TABLE t1 (a int);
CREATE TABLE
postgres[8765]=# CREATE TABLE t2 (a int);
CREATE TABLE
postgres[8765]=# INSERT INTO t1 VALUES (1);
INSERT 0 1
postgres[8765]=# INSERT INTO t2 VALUES (1);
INSERT 0 1
postgres[8765]=# CREATE PUBLICATION mypub1 FOR TABLE t1;
CREATE PUBLICATION
postgres[8765]=# CREATE PUBLICATION mypub2 FOR TABLE t2;
CREATE PUBLICATION
(2) created subscription on the subscriber
postgres[9812]=# CREATE TABLE t1 (a int);
CREATE TABLE
postgres[9812]=# CREATE TABLE t2 (a int);
CREATE TABLE
postgres[9812]=# CREATE SUBSCRIPTION mysub1 CONNECTION 'host=localhost port=8765 dbname=postgres' PUBLICATION mypub1;
NOTICE: created replication slot "mysub1" on publisher
CREATE SUBSCRIPTION
postgres[9812]=# CREATE SUBSCRIPTION mysub2 CONNECTION 'host=localhost port=8765 dbname=postgres' PUBLICATION mypub2;
NOTICE: created replication slot "mysub2" on publisher
CREATE SUBSCRIPTION
postgres[9812]=# TABLE t1;
a
---
1
(1 row)
postgres[9812]=# TABLE t2;
a
---
1
(1 row)
(3) insert into table t1, t2 on the publisher
postgres[8765]=# INSERT INTO t1 VALUES (2);
INSERT 0 1
postgres[8765]=# INSERT INTO t2 VALUES (2);
INSERT 0 1
(4) inserted data in (3) is received in the subscriber table t1, t2
postgres[9812]=# TABLE t1;
a
---
1
2
(2 rows)
postgres[9812]=# TABLE t2;
a
---
1
2
(2 rows)
(5) alter publication to drop table, we insert a record into t1 on publisher
postgres[8765]=# ALTER PUBLICATION mypub1 DROP TABLE t1;
ALTER PUBLICATION
postgres[8765]=# ALTER PUBLICATION mypub2 DROP TABLE t2;
ALTER PUBLICATION
postgres[8765]=# INSERT INTO t1 VALUES (3);
INSERT 0 1
(6) check the data on subscriber
postgres[9812]=# TABLE t1;
a
---
1
2
3
(3 rows)
postgres[9812]=# TABLE t2;
a
---
1
2
(2 rows)
(7) refresh subscription on the subscriber
postgres[9812]=# ALTER SUBSCRIPTION mysub1 REFRESH PUBLICATION;
ALTER SUBSCRIPTION
postgres[9812]=# ALTER SUBSCRIPTION mysub2 REFRESH PUBLICATION;
ALTER SUBSCRIPTION
(8) insert into table t1, t2 on the publisher
postgres[8765]=# INSERT INTO t1 VALUES (4);
INSERT 0 1
postgres[8765]=# INSERT INTO t2 VALUES (4);
INSERT 0 1
(9) the newly inserted data in (5), (7) for table t1 shipped to subscriber, however
t2 doesn’t
postgres[9812]=# TABLE t1;
a
---
1
2
3
4
(4 rows)
postgres[9812]=# TABLE t2;
a
---
1
2
(2 rows)
It might be a bug.
--
Regrads,
Japin Li.
ChengDu WenWu Information Technology Co.,Ltd.
On Mon, Jan 11, 2021 at 4:29 PM Li Japin <japinli@hotmail.com> wrote:
On Jan 11, 2021, at 5:06 PM, Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> wrote:
On Mon, Jan 11, 2021 at 1:29 PM japin <japinli@hotmail.com> wrote:
Sorry, I misunderstood. After the test (ce6a71fa530). I found that if we do not insert data
between step (5) and (6), it will not ship the new records, however, if we insert
data between step (5) and (6), it will ship the new records.
..
It might be a bug.
Can you check pg_publication_rel and pg_subscription_rel? Also, this
is not related to the feature proposed in this thread, so it is better
to start a new thread to conclude whether this is a bug or not.
--
With Regards,
Amit Kapila.
On Mon, Jan 11, 2021 at 5:25 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Jan 11, 2021 at 4:29 PM Li Japin <japinli@hotmail.com> wrote:
On Jan 11, 2021, at 5:06 PM, Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> wrote:
On Mon, Jan 11, 2021 at 1:29 PM japin <japinli@hotmail.com> wrote:
Sorry, I misunderstood. After the test (ce6a71fa530). I found that if we do not insert data
between step (5) and (6), it will not ship the new records, however, if we insert
data between step (5) and (6), it will ship the new records...
It might be a bug.
Can you check pg_publication_rel and pg_subscription_rel? Also, this
is not related to the feature proposed in this thread, so it is better
to start a new thread to conclude whether this is a bug or not.
Thanks Amit, sure I will verify and start a new thread.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Jan 11, 2021 at 5:28 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Mon, Jan 11, 2021 at 5:25 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Jan 11, 2021 at 4:29 PM Li Japin <japinli@hotmail.com> wrote:
On Jan 11, 2021, at 5:06 PM, Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> wrote:
On Mon, Jan 11, 2021 at 1:29 PM japin <japinli@hotmail.com> wrote:
Sorry, I misunderstood. After the test (ce6a71fa530). I found that if we do not insert data
between step (5) and (6), it will not ship the new records, however, if we insert
data between step (5) and (6), it will ship the new records...
It might be a bug.
Can you check pg_publication_rel and pg_subscription_rel? Also, this
is not related to the feature proposed in this thread, so it is better
to start a new thread to conclude whether this is a bug or not.Thanks Amit, sure I will verify and start a new thread.
I started a new thread [1]/messages/by-id/CALj2ACV+0UFpcZs5czYgBpujM9p0Hg1qdOZai_43OU7bqHU_xw@mail.gmail.com for this, please have a look.
[1]: /messages/by-id/CALj2ACV+0UFpcZs5czYgBpujM9p0Hg1qdOZai_43OU7bqHU_xw@mail.gmail.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Hi Vignesh,
I had a look at the patch, please consider following comments.
On Thu, Jan 7, 2021 at 10:03 PM vignesh C <vignesh21@gmail.com> wrote:
Hi,
This feature adds schema option while creating publication. Users will
be able to specify one or more schemas while creating publication,
when the user specifies schema option, then the data changes for the
tables present in the schema specified by the user will be replicated
to the subscriber. Few examples have been listed below:Create a publication that publishes all changes for all the tables
present in production schema:
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA production;Should it be FOR TABLES IN SCHEMA instead of FOR ALL TABLES SCHEMA?
Create a publication that publishes all changes for all the tables
present in marketing and sales schemas:
CREATE PUBLICATION sales_publication FOR ALL TABLES SCHEMA marketing,
sales;Add some schemas to the publication:
ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;As per current implementation this command fails even if one of the
schemas does not
exist. I think this is counterintuitive, it should throw a warning and
continue adding the rest.
Drop some schema from the publication:
ALTER PUBLICATION production_quarterly_publication DROP SCHEMA
production_july;Same for drop schema, if one of these schemas does not exist in
publication,
the entire DROP operation is aborted.
Thank you,
Rahila Syed
On Mon, Jan 11, 2021 at 11:45 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Sun, Jan 10, 2021 at 11:21 PM vignesh C <vignesh21@gmail.com> wrote:
On Sat, Jan 9, 2021 at 8:08 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I think this feature can be useful, in case a user has a lot of tables
to publish inside a schema. Having said that, I wonder if this feature
mandates users to create the same schema with same
permissions/authorizations manually on the subscriber, because logical
replication doesn't propagate any ddl's so are the schema or schema
changes? Or is it that the list of tables from the publisher can go
into a different schema on the subscriber?DDL's will not be propagated to the subscriber. Users have to create
the schema & tables in the subscriber. No change in
Permissions/authorizations handling, it will be the same as the
existing behavior for relations.Looks like the existing behaviour already requires users to create the
schema on the subscriber when publishing the tables from that schema.
Otherwise, an error is thrown on the subscriber [1].[1] on publisher:
CREATE SCHEMA myschema;
CREATE TABLE myschema.t1(a1 int, b1 int);
INSERT INTO myschema.t1_myschema SELECT i, i+10 FROM generate_series(1,10) i;
CREATE PUBLICATION testpub FOR TABLE myschema.t1;on subscriber:
postgres=# CREATE SUBSCRIPTION testsub CONNECTION 'host=localhost
dbname=postgres user=bharath port=5432' PUBLICATION testpub;
ERROR: schema "myschema" does not exist
CREATE SCHEMA myschema;
CREATE TABLE myschema.t1(a1 int, b1 int);
postgres=# CREATE SUBSCRIPTION testsub CONNECTION 'host=localhost
dbname=postgres user=bharath port=5432' PUBLICATION testpub;
NOTICE: created replication slot "testsub" on publisher
CREATE SUBSCRIPTION
Yes this feature will also have the same behavior, DDL creation should
be taken care of by DBA similar to how it is handled may be using
pg_dump or use sql scripts/statements to update.
Since the schema can have other objects such as data types, functions,
operators, I'm sure with your feature, non-table objects will be
skipped.Yes, only table data will be sent to subscribers, non-table objects
will be skipped.Looks like the existing CREATE PUBLICATION FOR ALL TABLES, which is
for all the tables in the database, does this i.e. skips non-table
objects and temporary tables, foreign tables and so on. So, your
feature also can behave the same way, but within the scope of the
given schema/s.
Yes, it will support only normal tables. Non table objects, foreign
tables & temporary tables will not be supported.
As Amit pointed out earlier, the behaviour when schema dropped, I
think we should also consider when schema is altered, say altered to a
different name, maybe we should change that in the publication too.I agree that when schema is altered the renamed schema should be
reflected in the publication.I think, it's not only making sure that the publisher side has the new
altered schema, but also the subscriber needs those alters. Having
said that, since these alters come under DDL changes and in logical
replication we don't publish the scheme changes to the subscriber, we
may not need to anything extra for informing the schema alters to the
subscriber from the publisher, the users might have to do the same
schema alter on the subscriber and then a ALTER SUBSCRIPTION testsub
REFRESH PUBLICATION; should work for them? If this understanding is
correct, then we should document this.
Yes, alter schema changes will be reflected in the publication, the
corresponding change needs to be done by the user on the subscriber
side. Once a user does ALTER SUBSCRIPTION testsub REFRESH PUBLICATION,
the new altered schema changes will be reflected in the subscriber. I
will update the documentation that user need to take care for
subscription refresh.
In general, what happens if we have some temporary tables or foreign
tables inside the schema, will they be allowed to send the data to
subscribers?Temporary tables & foreign tables will not be added to the publications.
Yes the existing logical replication framework doesn't allow
replication of temporary, unlogged, foreign tables and other non-table
relations such as materialized views, indexes etc [1]. The CREATE
PUBLICATION statement either fails in check_publication_add_relation
or before that.CREATE PUBLICATION testpub FOR TABLE tab1, throwing the error if the
single table tab1 is any of the above restricted tables, seems fine.
But, if there's a list of tables with CREATE PUBLICATION testpub FOR
TABLE normal_tab1, temp_tab2, normal_tab3, foreign_tab4,
unlogged_tab5, normal_tab6, normal_tab7 ......; This query fails on
first encounter of the restricted table, say at temp_tab2. Whereas,
CREATE PUBLICATION testpub FOR ALL TABLES; would skip the restricted
tables and continue to add the accepted tables into the publication
within the database.IMHO, if there's a list of tables specified with FOR TABLE, then
instead of throwing an error in case of any restricted table, we can
issue a warning and continue with the addition of accepted tables into
the publication. If done, this behaviour will be in sync with FOR ALL
TABLES;Thoughts? If okay, I can work on a patch.
I feel we can start a new thread for this to seek opinion if this base
change is required and reach consensus.
Related to error messages: when foreign table is specified in CREATE
PUBLICATION statement, then "ERROR: "f1" is not a table", is thrown
[1], how about the error message "ERROR: foerign table "f1" cannot be
replicated". In general, it would be good if we could have the error
messages something like in [2] instead of the existing [1].Thoughts? If okay, I can work on a patch.
I feel we can start a new thread for this to seek opinion and reach consensus.
[1]
t1 is a temporary table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: table "t1" cannot be replicated
DETAIL: Temporary and unlogged relations cannot be replicated.t1 is an unlogged table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: table "t1" cannot be replicated
DETAIL: Temporary and unlogged relations cannot be replicated.f1 is a foreign table:
postgres=# CREATE PUBLICATION testpub FOR TABLE f1;
ERROR: "f1" is not a table
DETAIL: Only tables can be added to publications.mv1 is a mat view:
postgres=# CREATE PUBLICATION testpub FOR TABLE mv1;
ERROR: "mv1" is not a tableidx1 is an index:
postgres=# CREATE PUBLICATION testpub FOR TABLE idx1;
ERROR: "idx1" is an index[2]
t1 is a temporary table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: temporary table "t1" cannot be replicated
DETAIL: Temporary, unlogged and foreign relations cannot be replicated.t1 is an unlogged table:
postgres=# CREATE PUBLICATION testpub FOR TABLE t1;
ERROR: unlogged table "t1" cannot be replicated
DETAIL: Temporary, unlogged and foreign relations cannot be replicated.f1 is a foreign table:
postgres=# CREATE PUBLICATION testpub FOR TABLE f1;
ERROR: foreign table "f1" cannot be replicated
DETAIL: Temporary, unlogged and foreign relations cannot be replicated.Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?There is no provision for this currently.
The documentation [1] says, we can ALTER PUBLICATION testpub DROP
TABLE t1; which removes the table from the list of published tables,
but looks like it requires ALTER SUBSCRIPTION testsub REFRESH
PUBLICATION; for the changes to become effective on the subscriber. I
have done some testing for this case:
1) created publication for table t1, see \d+ t1, the associated
publication is visible in the output
2) created subscription on the subscriber, initial available data from
the publisher for table t1 is received
3) insert into table t1 on the publisher
4) inserted data in (3) is received in the subscriber table t1
5) alter publication to drop the table t1 on the publisher, see \d+
t1, there will not be any associated publication in the output
6) execute alter subscription refresh publication on the subscriber,
with the expectation that it should not receive the data from the
publisher for the table t1 since it's dropped from the publication in
(5)
7) insert into table t1 on the publisher
8) still the newly inserted data in (7) from the publisher, will be
received into the table t1 in the subscriberIIUC, the behaviour of ALTER PUBLICATION DROP TABLE from the docs and
the above use case, it looks like a bug to me. If I'm wrong, can
someone correct me?Thoughts?
I think you started a new thread for this, let's conclude on this there.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Thanks Rahila for your comments, please find my thoughts below.
On Tue, Jan 12, 2021 at 5:16 PM Rahila Syed <rahilasyed90@gmail.com> wrote:
Hi Vignesh,
I had a look at the patch, please consider following comments.
On Thu, Jan 7, 2021 at 10:03 PM vignesh C <vignesh21@gmail.com> wrote:
Hi,
This feature adds schema option while creating publication. Users will
be able to specify one or more schemas while creating publication,
when the user specifies schema option, then the data changes for the
tables present in the schema specified by the user will be replicated
to the subscriber. Few examples have been listed below:Create a publication that publishes all changes for all the tables
present in production schema:
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA
production;
Should it be FOR TABLES IN SCHEMA instead of FOR ALL TABLES SCHEMA?
For adding tables into publication we have syntax like:
CREATE PUBLICATION mypub FOR TABLE tbl1, tbl2;
For all tables we have syntax like:
CREATE PUBLICATION mypub FOR ALL TABLES;
Initial syntax that I proposed was:
CREATE PUBLICATION production_publication *FOR ALL TABLES SCHEMA*
production;
I feel the below syntax is better, as it is consistent with others:
CREATE PUBLICATION mypub *FOR SCHEMA* sch1, sch2;
Create a publication that publishes all changes for all the tables
present in marketing and sales schemas:
CREATE PUBLICATION sales_publication FOR ALL TABLES SCHEMA marketing,
sales;
Add some schemas to the publication:
ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june,
sales_june;
As per current implementation this command fails even if one of the
schemas does not
exist. I think this is counterintuitive, it should throw a warning and
continue adding the rest.
We have the similar behavior in case of adding non-existent table while
creating a publication:
CREATE PUBLICATION mypub3 FOR TABLE non_existent_table;
ERROR: relation "non_existent_table" does not exist
I feel we can keep the behavior similarly to maintain the consistency.
Drop some schema from the publication:
ALTER PUBLICATION production_quarterly_publication DROP SCHEMA
production_july;
Same for drop schema, if one of these schemas does not exist in
publication,
the entire DROP operation is aborted.
We have similar behavior in case of dropping non-existent table while
altering publication
alter publication mypub5 drop table test1,testx;
ERROR: relation "testx" does not exist
I feel we can keep the behavior similarly to maintain the consistency.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Sat, Jan 9, 2021 at 5:21 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Jan 8, 2021 at 4:32 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Jan 7, 2021 at 10:03 PM vignesh C <vignesh21@gmail.com> wrote:
This feature adds schema option while creating publication. Users will
be able to specify one or more schemas while creating publication,
when the user specifies schema option, then the data changes for the
tables present in the schema specified by the user will be replicated
to the subscriber. Few examples have been listed below:Create a publication that publishes all changes for all the tables
present in production schema:
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA production;Create a publication that publishes all changes for all the tables
present in marketing and sales schemas:
CREATE PUBLICATION sales_publication FOR ALL TABLES SCHEMA marketing, sales;Add some schemas to the publication:
ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;Drop some schema from the publication:
ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;Attached is a POC patch for the same. I felt this feature would be quite useful.
What do we do if the user Drops the schema? Do we automatically remove
it from the publication?I have not yet handled this scenario yet, I will handle this and
adding of tests in the next patch.
I have handled the above scenario(drop schema should automatically
remove the schema entry from publication schema relation) & addition
of tests in the new v2 patch attached.
Thoughts?
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v2-0001-Added-schema-level-support-for-publication.patchapplication/x-patch; name=v2-0001-Added-schema-level-support-for-publication.patchDownload
From d14e47368091d81f22ab11b94ba0716e0d918471 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh.c@enterprisedb.com>
Date: Mon, 18 Jan 2021 18:08:51 +0530
Subject: [PATCH v2] Added schema level support for publication.
This patch adds schema level support for publication. User can specify multiple
schemas with schema option. When user specifies schema option, then the tables
present in the schema specified will be selected by publisher for sending the
data to subscriber.
---
doc/src/sgml/ref/alter_publication.sgml | 45 +++-
doc/src/sgml/ref/create_publication.sgml | 31 ++-
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 138 +++++++++++++
src/backend/catalog/pg_publication.c | 134 +++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 266 +++++++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 1 +
src/backend/parser/gram.y | 76 ++++---
src/backend/utils/cache/syscache.c | 23 +++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 155 +++++++++++++-
src/bin/pg_dump/pg_dump.h | 17 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 110 +++++++++-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 16 +-
src/include/catalog/pg_publication_schema.h | 49 +++++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/parsenodes.h | 3 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 298 ++++++++++++++++++++++-----
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 87 +++++++-
31 files changed, 1401 insertions(+), 96 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b..d6199af 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants change which schemas are part of the
+ publication. The <literal>SET TABLE</literal> clause will replace the list
+ of schemas in the publication with the specified one. The <literal>ADD
+ SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses will add and
+ remove one or more schemas from the publication. Note that adding schemas
+ to a publication that is already subscribed to will require a <literal>ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the subscribing side
+ in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -98,6 +112,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</varlistentry>
<varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
<para>
@@ -142,6 +165,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
+</programlisting></para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbc..09c079d 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -100,6 +101,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
<para>
@@ -222,6 +233,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+production schema:
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+marketing and sales schemas:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index c85f0ca..dc8a9eb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -67,8 +67,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 1a81e76..c93409b 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3414,6 +3414,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3553,6 +3554,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 2140151..94ed1f4 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1549,6 +1551,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2982,6 +2988,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 6d88b69..02661c4 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -828,6 +829,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -874,6 +879,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1117,6 +1125,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1947,51 @@ get_object_address_publication_rel(List *object,
}
/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%u\" in publication \"%s\" does not exist",
+ schemaoid, pubname)));
+ return address;
+ }
+
+ return address;
+}
+
+/*
* Find the ObjectAddress for a default ACL.
*/
static ObjectAddress
@@ -2206,6 +2263,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2298,6 +2356,9 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ objnode = (Node *) list_make2(linitial(name), linitial(args));
+ break;
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3897,6 +3958,40 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema prform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ prform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(prform->prpubid, false);
+ nspname = get_namespace_name(prform->prnspcid);
+ if (!nspname)
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ object->objectId);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4470,6 +4565,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5705,6 +5804,45 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema prform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ prform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(prform->prpubid, false);
+ nspname = get_namespace_name(prform->prnspcid);
+ if (!nspname)
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ object->objectId);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ if (objname)
+ *objname = list_make1(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5f8e1c6..4d46935 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,8 +28,10 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
@@ -214,6 +216,76 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_rel];
+ bool nulls[Natts_pg_publication_rel];
+ Oid prrelid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ prrelid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(prrelid);
+ values[Anum_pg_publication_schema_prpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_prnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, prrelid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -305,6 +377,47 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
+ * Gets list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result;
+ Relation pubrelsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubrelsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_prpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubrelsrel, PublicationSchemaPrnspcidPrpubidIndexId,
+ true, NULL, 1, &scankey);
+
+ result = NIL;
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubrel;
+
+ pubrel = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubrel->prnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubrelsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
List *
@@ -349,13 +462,16 @@ GetAllTablesPublications(void)
* root partitioned tables.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(Publication *publication)
{
Relation classRel;
ScanKeyData key[1];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ bool pubviaroot = publication->pubviaroot;
+ List *pubschemas = GetPublicationSchemas(publication->oid);
+
classRel = table_open(RelationRelationId, AccessShareLock);
@@ -371,6 +487,16 @@ GetAllTablesPublicationRelations(bool pubviaroot)
Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
Oid relid = relForm->oid;
+ /*
+ * If schema is specified by the user, check if the relation is present
+ * in one of the schema specified.
+ */
+ if (pubschemas)
+ {
+ if (!list_member_oid(pubschemas, relForm->relnamespace))
+ continue;
+ }
+
if (is_publishable_class(relid, relForm) &&
!(relForm->relispartition && pubviaroot))
result = lappend_oid(result, relid);
@@ -431,6 +557,8 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtables = pubform->pubtables;
+ pub->pubschemas = pubform->pubschemas;
ReleaseSysCache(tup);
@@ -530,8 +658,8 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ if (publication->alltables || publication->pubschemas)
+ tables = GetAllTablesPublicationRelations(publication);
else
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 2924949..e7c2745 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 5bde507..4673e8e 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2120,6 +2122,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2202,6 +2205,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c..6bb6a74 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(List *options,
@@ -212,6 +217,10 @@ CreatePublication(CreatePublicationStmt *stmt)
BoolGetDatum(pubactions.pubtruncate);
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ values[Anum_pg_publication_pubtables -1] =
+ stmt->tables ? BoolGetDatum(true) : BoolGetDatum(false);
+ values[Anum_pg_publication_pubschemas - 1] =
+ stmt->schemas ? BoolGetDatum(true) : BoolGetDatum(false);
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
@@ -226,6 +235,23 @@ CreatePublication(CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ ListCell *cell;
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ foreach(cell, stmt->schemas)
+ {
+ char *schema = strVal(lfirst(cell));
+ schemaoidlist = lappend_oid(schemaoidlist, get_namespace_oid(schema, false));
+ }
+
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -252,6 +278,30 @@ CreatePublication(CreatePublicationStmt *stmt)
return myself;
}
+static void
+UpdatePublicationTupleValue(Relation rel, HeapTuple tup, int col, bool value)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = BoolGetDatum(value);
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -370,14 +420,34 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubschemas)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (!pubform->pubtables)
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubtables,
+ true);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
+ {
+ List *tables;
PublicationDropTables(pubid, rels, false);
+ tables = GetPublicationRelations(pubid, PUBLICATION_PART_ROOT);
+ if (!list_length(tables))
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubtables,
+ false);
+
+ }
else /* DEFELEM_SET */
{
List *oldrelids = GetPublicationRelations(pubid,
@@ -422,16 +492,116 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PublicationAddTables(pubid, rels, true, stmt);
CloseTableList(delrels);
+
+ /* Update pubtables col to true */
+ if (!pubform->pubtables)
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubtables,
+ true);
}
CloseTableList(rels);
}
/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ foreach(cell, stmt->schemas)
+ {
+ char *schema = strVal(lfirst(cell));
+ schemaoidlist = lappend_oid(schemaoidlist, get_namespace_oid(schema, false));
+ }
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (!pubform->pubschemas)
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubschemas,
+ true);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ {
+ List *schemas;
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ schemas = GetPublicationSchemas(pubform->oid);
+ if (!list_length(schemas))
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubschemas,
+ false);
+ }
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldrelid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, schemaoidlist)
+ {
+ Oid newschemaid = lfirst_oid(newlc);
+
+ if (newschemaid == oldrelid)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ delschemas = lappend_oid(delschemas, oldrelid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+
+ if (!pubform->pubschemas)
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubschemas,
+ true);
+ }
+
+ return;
+}
+
+/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +630,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -499,6 +671,30 @@ RemovePublicationRelById(Oid proid)
}
/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid proid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(proid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ proid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
* add them to a publication.
@@ -633,6 +829,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
/*
+ * Add listed tables to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
+/*
* Remove listed tables from the publication.
*/
static void
@@ -667,6 +896,39 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
* Internal workhorse for changing a publication owner
*/
static void
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 6906714..b108b64 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a..c444910 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11602,6 +11602,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c9544..ce4b305 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -420,7 +420,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -9470,41 +9469,39 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA name_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
-
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
@@ -9515,6 +9512,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9549,6 +9551,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA name_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA name_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA name_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e4dc4ee..953ab6f 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPrnspcidPrpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_prnspcid,
+ Anum_pg_publication_schema_prpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index b0f02bc..17b132f 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 1f82c64..e21acb8 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2847,7 +2847,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 798d145..3924e92 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3882,6 +3882,8 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtables;
+ int i_pubschemas;
int i,
ntups;
@@ -3896,7 +3898,14 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubschemas, p.pubtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "FROM pg_publication p",
+ username_subquery);
+ else if (fout->remoteVersion >= 130000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
@@ -3932,6 +3941,8 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtables = PQfnumber(res, "pubtables");
+ i_pubschemas = PQfnumber(res, "pubschemas");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -3956,6 +3967,10 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtables =
+ (strcmp(PQgetvalue(res, i, i_pubtables), "t") == 0);
+ pubinfo[i].pubschemas =
+ (strcmp(PQgetvalue(res, i, i_pubschemas), "t") == 0);
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4066,6 +4081,102 @@ dumpPublication(Archive *fout, PublicationInfo *pubinfo)
}
/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 140000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the table. */
+ appendPQExpBuffer(query,
+ "SELECT pr.prnspcid, pr.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema pr, pg_publication p "
+ "WHERE pr.prnspcid = '%u'"
+ " AND p.oid = pr.prpubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not member of any publications. Clean up and return.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "prnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid prpubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(prpubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
+/*
* getPublicationTables
* get information about publication membership for dumpable tables.
*/
@@ -4153,6 +4264,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
}
/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
+/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
*/
@@ -10293,6 +10442,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (SubscriptionInfo *) dobj);
break;
@@ -18429,6 +18581,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 1290f96..f3c05ad 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -614,6 +615,8 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ bool pubtables;
+ bool pubschemas;
} PublicationInfo;
/*
@@ -628,6 +631,18 @@ typedef struct _PublicationRelInfo
} PublicationRelInfo;
/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
+/*
* The SubscriptionInfo struct is used to represent subscription.
*/
typedef struct _SubscriptionInfo
@@ -732,6 +747,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb..13a6fcd 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index caf9756..f2d22e4 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2884,6 +2884,39 @@ describeOneTableDetails(const char *schemaname,
printTableAddFooter(&cont, buf.data);
}
PQclear(result);
+
+ if (pset.sversion >= 140000)
+ {
+ int pub_schema_tuples;
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.oid in \n"
+ "(SELECT s.prpubid FROM \n"
+ "pg_catalog.pg_class c, \n"
+ "pg_catalog.pg_publication_schema s \n"
+ "where c.oid = '%s' AND \n"
+ "c.relnamespace = s.prnspcid)",
+ oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (!tuples && pub_schema_tuples > 0)
+ printTableAddFooter(&cont, _("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ printTableAddFooter(&cont, buf.data);
+ }
+ PQclear(result);
+ }
}
}
@@ -5746,7 +5779,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, false};
if (pset.sversion < 100000)
{
@@ -5781,7 +5814,12 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
-
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtables AS \"%s\",\n"
+ " pubschemas AS \"%s\"",
+ gettext_noop("Tables"),
+ gettext_noop("Schemas"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -5823,6 +5861,8 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtables;
+ bool has_pubschemas;
if (pset.sversion < 100000)
{
@@ -5836,6 +5876,8 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtables = (pset.sversion >= 140000);
+ has_pubschemas= (pset.sversion >= 140000);
initPQExpBuffer(&buf);
@@ -5849,6 +5891,13 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtables)
+ appendPQExpBufferStr(&buf,
+ ", pubtables");
+ if (has_pubschemas)
+ appendPQExpBufferStr(&buf,
+ ", pubschemas");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -5891,6 +5940,8 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool pubtables;
+ bool pubschemas;
int j;
PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
@@ -5900,6 +5951,16 @@ describePublications(const char *pattern)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtables)
+ {
+ pubtables = strcmp(PQgetvalue(res, i, 9), "t") == 0;
+ ncols++;
+ }
+ if (has_pubschemas)
+ {
+ pubschemas = strcmp(PQgetvalue(res, i, 10), "t") == 0;
+ ncols++;
+ }
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -5914,6 +5975,10 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtables)
+ printTableAddHeader(&cont, gettext_noop("Pubtables"), true, align);
+ if (has_pubschemas)
+ printTableAddHeader(&cont, gettext_noop("Pubschemas"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -5924,8 +5989,13 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtables)
+ printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
+ if (has_pubschemas)
+ printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
- if (!puballtables)
+ /* Prior to version 14 check was based on all tables */
+ if ((has_pubtables && pubtables) || (!has_pubtables && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -5962,6 +6032,40 @@ describePublications(const char *pattern)
}
PQclear(tabres);
}
+ else if (pubschemas)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.prnspcid\n"
+ " AND ps.prpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+
+ tabres = PSQLexec(buf.data);
+ if (!tabres)
+ {
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
+ }
+ else
+ tables = PQntuples(tabres);
+
+ if (tables > 0)
+ printTableAddFooter(&cont, _("Schemas:"));
+
+ for (j = 0; j < tables; j++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(tabres, j, 0));
+
+ printTableAddFooter(&cont, buf.data);
+ }
+ PQclear(tabres);
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index f272e2c..11af0c8 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -131,6 +131,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 0dd50fe..da3bd01 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -20,6 +20,7 @@
#include "catalog/genbki.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
+#include "utils/array.h"
/* ----------------
* pg_publication definition. cpp turns this into
@@ -54,6 +55,12 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* indicates publication is for specific tables */
+ bool pubtables;
+
+ /* indicates publication is for specific schema tables */
+ bool pubschemas;
} FormData_pg_publication;
/* ----------------
@@ -83,6 +90,8 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ bool pubtables;
+ bool pubschemas;
} Publication;
extern Publication *GetPublication(Oid pubid);
@@ -106,15 +115,16 @@ typedef enum PublicationPartOpt
} PublicationPartOpt;
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetAllTablesPublicationRelations(Publication *publication);
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
-
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
-
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000..73d5815
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid prpubid; /* Oid of the publication */
+ Oid prnspcid; /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX(pg_publication_schema_oid_index, 8902, on pg_publication_schema using btree(oid oid_ops));
+#define PublicationSchemaObjectIndexId 8902
+DECLARE_UNIQUE_INDEX(pg_publication_schema_prrelid_prpubid_index, 8903, on pg_publication_schema using btree(prnspcid oid_ops, prpubid oid_ops));
+#define PublicationSchemaPrnspcidPrpubidIndexId 8903
+
+#endif /* PG_PUBLICATION_REL_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 00e2e62..581fedb 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -21,6 +21,7 @@
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index dc2bb40..8f9639e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1747,6 +1747,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3523,6 +3524,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3537,6 +3539,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348..1ba2952 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a..49ea22f 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7..3349de7 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | Tables | Schemas
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+--------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | f | f
+ testpub_default | regress_publication_user | f | f | t | f | 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 | Truncates | Via root | Tables | Schemas
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+--------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | f | f
+ testpub_default | regress_publication_user | f | t | t | t | f | f | f | f
(2 rows)
--- adding tables
@@ -85,10 +85,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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | t | t | t | f | f | f | f | f
(1 row)
DROP TABLE testpub_tbl2;
@@ -100,19 +100,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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | 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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | t | f
Tables:
"public.testpub_tbl3"
@@ -131,10 +131,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | t | f
Tables:
"public.testpub_parted"
@@ -147,10 +147,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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | t | t | f
Tables:
"public.testpub_parted"
@@ -170,10 +170,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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | t | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -211,10 +211,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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | f | f | t | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -255,13 +255,12 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | f | f | f | f
(1 row)
-- fail - must be owner of publication
@@ -271,27 +270,224 @@ 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 | Truncates | Via root | Tables | Schemas
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+--------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | 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 | Truncates | Via root | Tables | Schemas
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+--------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | f | f
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+RESET client_min_messages;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+RESET client_min_messages;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+--- Check create publication on a schema that does not exist.
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a object which is not schema.
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | f
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961..fe5a038 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e..56d9b85 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075..ae23f9f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,97 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+RESET client_min_messages;
+\dRp+ testpub1_forschema
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+RESET client_min_messages;
+\dRp+ testpub2_forschema
+
+--- Check create publication on a schema that does not exist.
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a object which is not schema.
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication.
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication.
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
--
1.8.3.1
On Sat, Jan 9, 2021 at 8:08 PM Bharath Rupireddy <
bharath.rupireddyforpostgres@gmail.com> wrote:
As Amit pointed out earlier, the behaviour when schema dropped, I
think we should also consider when schema is altered, say altered to a
different name, maybe we should change that in the publication too.
This scenario is handled now in the patch posted at [1]/messages/by-id/CALDaNm02=k8K_ZSN7_dyVHyMTW4B5hOaeo2PzdWG=a7GtLH0oA@mail.gmail.com.
Say a user has a schema with 121 tables in it, and wants to replicate
only 120 or 199 or even lesser tables out of it, so can we have some
skip option to the new syntax, something like below?
CREATE PUBLICATION production_publication FOR ALL TABLES SCHEMA
production WITH skip = marketing, accounts, sales; --> meaning is,
replicate all the tables in the schema production except marketing,
accounts, sales tables.
I have not yet handled this, I'm working on this and will try post a patch
for this in the next version.
[1]: /messages/by-id/CALDaNm02=k8K_ZSN7_dyVHyMTW4B5hOaeo2PzdWG=a7GtLH0oA@mail.gmail.com
/messages/by-id/CALDaNm02=k8K_ZSN7_dyVHyMTW4B5hOaeo2PzdWG=a7GtLH0oA@mail.gmail.com
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Hi Vignesh,
I have handled the above scenario(drop schema should automatically
remove the schema entry from publication schema relation) & addition
of tests in the new v2 patch attached.
Thoughts?
Please see some initial comments:
1. I think there should be more tests to show that the schema data is
actually replicated
to the subscriber. Currently, I am not seeing the data being replicated
when I use FOR SCHEMA.
2. How does replication behave when a table is added or removed from a
subscribed schema
using ALTER TABLE SET SCHEMA?
3. Can we have a default schema like a public or current schema that gets
replicated in case the user didn't
specify one, this can be handy to replicate current schema tables.
4. + The fourth, fifth and sixth variants change which schemas are part
of the
+ publication. The <literal>SET TABLE</literal> clause will replace the
list
+ of schemas in the publication with the specified one. The <literal>ADD
There is a typo above s/SET TABLE/SET SCHEMA
Thank you,
Rahila Syed
Thanks Rahila for your comments. Please find my thoughts below:
On Wed, Jan 20, 2021 at 6:27 PM Rahila Syed <rahilasyed90@gmail.com> wrote:
Hi Vignesh,
I have handled the above scenario(drop schema should automatically
remove the schema entry from publication schema relation) & addition
of tests in the new v2 patch attached.
Thoughts?Please see some initial comments:
1. I think there should be more tests to show that the schema data is actually replicated
to the subscriber. Currently, I am not seeing the data being replicated when I use FOR SCHEMA.
I will fix this issue and include more tests in my next version of the patch.
2. How does replication behave when a table is added or removed from a subscribed schema
using ALTER TABLE SET SCHEMA?
I would like to keep the behavior similar to the table behavior. I
will post more details for this along with my next version of the
patch.
3. Can we have a default schema like a public or current schema that gets replicated in case the user didn't
specify one, this can be handy to replicate current schema tables.
It looks like a good use case, I will check on the feasibility of this
and try to implement this.
4. + The fourth, fifth and sixth variants change which schemas are part of the + publication. The <literal>SET TABLE</literal> clause will replace the list + of schemas in the publication with the specified one. The <literal>ADDThere is a typo above s/SET TABLE/SET SCHEMA
I will fix this in the next version of the patch.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Fri, Jan 22, 2021 at 10:01 AM vignesh C <vignesh21@gmail.com> wrote:
Thanks Rahila for your comments. Please find my thoughts below:
On Wed, Jan 20, 2021 at 6:27 PM Rahila Syed <rahilasyed90@gmail.com> wrote:
Hi Vignesh,
I have handled the above scenario(drop schema should automatically
remove the schema entry from publication schema relation) & addition
of tests in the new v2 patch attached.
Thoughts?Please see some initial comments:
1. I think there should be more tests to show that the schema data is actually replicated
to the subscriber. Currently, I am not seeing the data being replicated when I use FOR SCHEMA.I will fix this issue and include more tests in my next version of the patch.
Modified to handle this and also added a few more tests.
2. How does replication behave when a table is added or removed from a subscribed schema
using ALTER TABLE SET SCHEMA?I would like to keep the behavior similar to the table behavior. I
will post more details for this along with my next version of the
patch.
If a table is set to a different schema, after the schema change table
data will not be sent to the subscriber.
When a new table is added to the published schema, the table data will
be sent by the publisher, subscriber will not apply the changes. If
the change needs to be reflected, subscriber's publication should be
refreshed using "alter subscription mysub1 refresh publication". This
relation will be reflected in the subscriber relation when the
subscriber's publication is refreshed.
If a table is dropped, there is no impact on subscriber, This relation
will be present in pg_subscriber_rel after refreshing subscriber
publication.
3. Can we have a default schema like a public or current schema that gets replicated in case the user didn't
specify one, this can be handy to replicate current schema tables.It looks like a good use case, I will check on the feasibility of this
and try to implement this.
This can be done, I will handle this later.
4. + The fourth, fifth and sixth variants change which schemas are part of the + publication. The <literal>SET TABLE</literal> clause will replace the list + of schemas in the publication with the specified one. The <literal>ADDThere is a typo above s/SET TABLE/SET SCHEMA
I will fix this in the next version of the patch.
Modified it.
I have separated the tests and documentation into a separate patch to
make review easier. Attached v3 patch with the fixes.
Thoughts?
Regards,
Vignesh
Attachments:
v3-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Added-schema-level-support-for-publication.patchDownload
From 72255fcb52cfba2d284b8402b64c118160b35b9f Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh.c@enterprisedb.com>
Date: Sun, 31 Jan 2021 22:47:38 +0530
Subject: [PATCH v3 1/2] Added schema level support for publication.
This patch adds schema level support for publication. User can specify multiple
schemas with schema option. When user specifies schema option, then the tables
present in the schema specified will be selected by publisher for sending the
data to subscriber.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 138 +++++++++++++++
src/backend/catalog/pg_publication.c | 134 +++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 266 +++++++++++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 1 +
src/backend/parser/gram.y | 83 ++++++---
src/backend/replication/pgoutput/pgoutput.c | 12 ++
src/backend/utils/cache/syscache.c | 23 +++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 155 +++++++++++++++-
src/bin/pg_dump/pg_dump.h | 17 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 110 +++++++++++-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 16 +-
src/include/catalog/pg_publication_schema.h | 49 +++++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/parsenodes.h | 3 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/publication.out | 100 +++++------
src/test/regress/expected/sanity_check.out | 1 +
27 files changed, 1055 insertions(+), 91 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index c85f0ca..dc8a9eb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -67,8 +67,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index f3c1ca1..8322d50 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3417,6 +3417,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3556,6 +3557,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 2140151..94ed1f4 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1549,6 +1551,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2982,6 +2988,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 6d88b69..02661c4 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -828,6 +829,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -874,6 +879,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1117,6 +1125,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1947,51 @@ get_object_address_publication_rel(List *object,
}
/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%u\" in publication \"%s\" does not exist",
+ schemaoid, pubname)));
+ return address;
+ }
+
+ return address;
+}
+
+/*
* Find the ObjectAddress for a default ACL.
*/
static ObjectAddress
@@ -2206,6 +2263,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2298,6 +2356,9 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ objnode = (Node *) list_make2(linitial(name), linitial(args));
+ break;
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3897,6 +3958,40 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema prform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ prform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(prform->prpubid, false);
+ nspname = get_namespace_name(prform->prnspcid);
+ if (!nspname)
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ object->objectId);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4470,6 +4565,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5705,6 +5804,45 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema prform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ prform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(prform->prpubid, false);
+ nspname = get_namespace_name(prform->prnspcid);
+ if (!nspname)
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ object->objectId);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ if (objname)
+ *objname = list_make1(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5f8e1c6..4d46935 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,8 +28,10 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
@@ -214,6 +216,76 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_rel];
+ bool nulls[Natts_pg_publication_rel];
+ Oid prrelid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ prrelid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(prrelid);
+ values[Anum_pg_publication_schema_prpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_prnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, prrelid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -305,6 +377,47 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
+ * Gets list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result;
+ Relation pubrelsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubrelsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_prpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubrelsrel, PublicationSchemaPrnspcidPrpubidIndexId,
+ true, NULL, 1, &scankey);
+
+ result = NIL;
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubrel;
+
+ pubrel = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubrel->prnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubrelsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
List *
@@ -349,13 +462,16 @@ GetAllTablesPublications(void)
* root partitioned tables.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(Publication *publication)
{
Relation classRel;
ScanKeyData key[1];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ bool pubviaroot = publication->pubviaroot;
+ List *pubschemas = GetPublicationSchemas(publication->oid);
+
classRel = table_open(RelationRelationId, AccessShareLock);
@@ -371,6 +487,16 @@ GetAllTablesPublicationRelations(bool pubviaroot)
Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
Oid relid = relForm->oid;
+ /*
+ * If schema is specified by the user, check if the relation is present
+ * in one of the schema specified.
+ */
+ if (pubschemas)
+ {
+ if (!list_member_oid(pubschemas, relForm->relnamespace))
+ continue;
+ }
+
if (is_publishable_class(relid, relForm) &&
!(relForm->relispartition && pubviaroot))
result = lappend_oid(result, relid);
@@ -431,6 +557,8 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtables = pubform->pubtables;
+ pub->pubschemas = pubform->pubschemas;
ReleaseSysCache(tup);
@@ -530,8 +658,8 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ if (publication->alltables || publication->pubschemas)
+ tables = GetAllTablesPublicationRelations(publication);
else
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 2924949..e7c2745 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 5bde507..4673e8e 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2120,6 +2122,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2202,6 +2205,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c..6bb6a74 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(List *options,
@@ -212,6 +217,10 @@ CreatePublication(CreatePublicationStmt *stmt)
BoolGetDatum(pubactions.pubtruncate);
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ values[Anum_pg_publication_pubtables -1] =
+ stmt->tables ? BoolGetDatum(true) : BoolGetDatum(false);
+ values[Anum_pg_publication_pubschemas - 1] =
+ stmt->schemas ? BoolGetDatum(true) : BoolGetDatum(false);
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
@@ -226,6 +235,23 @@ CreatePublication(CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ ListCell *cell;
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ foreach(cell, stmt->schemas)
+ {
+ char *schema = strVal(lfirst(cell));
+ schemaoidlist = lappend_oid(schemaoidlist, get_namespace_oid(schema, false));
+ }
+
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -252,6 +278,30 @@ CreatePublication(CreatePublicationStmt *stmt)
return myself;
}
+static void
+UpdatePublicationTupleValue(Relation rel, HeapTuple tup, int col, bool value)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = BoolGetDatum(value);
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -370,14 +420,34 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubschemas)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (!pubform->pubtables)
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubtables,
+ true);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
+ {
+ List *tables;
PublicationDropTables(pubid, rels, false);
+ tables = GetPublicationRelations(pubid, PUBLICATION_PART_ROOT);
+ if (!list_length(tables))
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubtables,
+ false);
+
+ }
else /* DEFELEM_SET */
{
List *oldrelids = GetPublicationRelations(pubid,
@@ -422,16 +492,116 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PublicationAddTables(pubid, rels, true, stmt);
CloseTableList(delrels);
+
+ /* Update pubtables col to true */
+ if (!pubform->pubtables)
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubtables,
+ true);
}
CloseTableList(rels);
}
/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ foreach(cell, stmt->schemas)
+ {
+ char *schema = strVal(lfirst(cell));
+ schemaoidlist = lappend_oid(schemaoidlist, get_namespace_oid(schema, false));
+ }
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (!pubform->pubschemas)
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubschemas,
+ true);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ {
+ List *schemas;
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ schemas = GetPublicationSchemas(pubform->oid);
+ if (!list_length(schemas))
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubschemas,
+ false);
+ }
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldrelid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, schemaoidlist)
+ {
+ Oid newschemaid = lfirst_oid(newlc);
+
+ if (newschemaid == oldrelid)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ delschemas = lappend_oid(delschemas, oldrelid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+
+ if (!pubform->pubschemas)
+ UpdatePublicationTupleValue(rel, tup, Anum_pg_publication_pubschemas,
+ true);
+ }
+
+ return;
+}
+
+/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +630,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -499,6 +671,30 @@ RemovePublicationRelById(Oid proid)
}
/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid proid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(proid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ proid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
* add them to a publication.
@@ -633,6 +829,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
/*
+ * Add listed tables to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
+/*
* Remove listed tables from the publication.
*/
static void
@@ -667,6 +896,39 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
* Internal workhorse for changing a publication owner
*/
static void
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 6906714..b108b64 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 36747e7..f4593af 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11709,6 +11709,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7574d54..88081fd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -421,7 +421,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -9466,46 +9465,49 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA name_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
-
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
@@ -9516,6 +9518,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9550,6 +9557,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA name_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA name_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA name_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 79765f9..458a04b 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1005,6 +1005,18 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ if (pub->pubschemas)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ List *pubschemas = GetPublicationSchemas(pub->oid);
+ if (list_member_oid(pubschemas, schemaId))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
+
if (!publish)
{
bool ancestor_published = false;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e4dc4ee..953ab6f 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPrnspcidPrpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_prnspcid,
+ Anum_pg_publication_schema_prpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index b0f02bc..17b132f 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 1f82c64..e21acb8 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2847,7 +2847,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 798d145..3924e92 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3882,6 +3882,8 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtables;
+ int i_pubschemas;
int i,
ntups;
@@ -3896,7 +3898,14 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubschemas, p.pubtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "FROM pg_publication p",
+ username_subquery);
+ else if (fout->remoteVersion >= 130000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
@@ -3932,6 +3941,8 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtables = PQfnumber(res, "pubtables");
+ i_pubschemas = PQfnumber(res, "pubschemas");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -3956,6 +3967,10 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtables =
+ (strcmp(PQgetvalue(res, i, i_pubtables), "t") == 0);
+ pubinfo[i].pubschemas =
+ (strcmp(PQgetvalue(res, i, i_pubschemas), "t") == 0);
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4066,6 +4081,102 @@ dumpPublication(Archive *fout, PublicationInfo *pubinfo)
}
/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 140000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the table. */
+ appendPQExpBuffer(query,
+ "SELECT pr.prnspcid, pr.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema pr, pg_publication p "
+ "WHERE pr.prnspcid = '%u'"
+ " AND p.oid = pr.prpubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not member of any publications. Clean up and return.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "prnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid prpubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(prpubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
+/*
* getPublicationTables
* get information about publication membership for dumpable tables.
*/
@@ -4153,6 +4264,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
}
/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
+/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
*/
@@ -10293,6 +10442,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (SubscriptionInfo *) dobj);
break;
@@ -18429,6 +18581,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 1290f96..f3c05ad 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -614,6 +615,8 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ bool pubtables;
+ bool pubschemas;
} PublicationInfo;
/*
@@ -628,6 +631,18 @@ typedef struct _PublicationRelInfo
} PublicationRelInfo;
/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
+/*
* The SubscriptionInfo struct is used to represent subscription.
*/
typedef struct _SubscriptionInfo
@@ -732,6 +747,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb..13a6fcd 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 20af5a9..f9a1283 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2884,6 +2884,39 @@ describeOneTableDetails(const char *schemaname,
printTableAddFooter(&cont, buf.data);
}
PQclear(result);
+
+ if (pset.sversion >= 140000)
+ {
+ int pub_schema_tuples;
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.oid in \n"
+ "(SELECT s.prpubid FROM \n"
+ "pg_catalog.pg_class c, \n"
+ "pg_catalog.pg_publication_schema s \n"
+ "where c.oid = '%s' AND \n"
+ "c.relnamespace = s.prnspcid)",
+ oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (!tuples && pub_schema_tuples > 0)
+ printTableAddFooter(&cont, _("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ printTableAddFooter(&cont, buf.data);
+ }
+ PQclear(result);
+ }
}
}
@@ -5829,7 +5862,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, false};
if (pset.sversion < 100000)
{
@@ -5864,7 +5897,12 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
-
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtables AS \"%s\",\n"
+ " pubschemas AS \"%s\"",
+ gettext_noop("Tables"),
+ gettext_noop("Schemas"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -5906,6 +5944,8 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtables;
+ bool has_pubschemas;
if (pset.sversion < 100000)
{
@@ -5919,6 +5959,8 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtables = (pset.sversion >= 140000);
+ has_pubschemas= (pset.sversion >= 140000);
initPQExpBuffer(&buf);
@@ -5932,6 +5974,13 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtables)
+ appendPQExpBufferStr(&buf,
+ ", pubtables");
+ if (has_pubschemas)
+ appendPQExpBufferStr(&buf,
+ ", pubschemas");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -5974,6 +6023,8 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool pubtables;
+ bool pubschemas;
int j;
PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
@@ -5983,6 +6034,16 @@ describePublications(const char *pattern)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtables)
+ {
+ pubtables = strcmp(PQgetvalue(res, i, 9), "t") == 0;
+ ncols++;
+ }
+ if (has_pubschemas)
+ {
+ pubschemas = strcmp(PQgetvalue(res, i, 10), "t") == 0;
+ ncols++;
+ }
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -5997,6 +6058,10 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtables)
+ printTableAddHeader(&cont, gettext_noop("Pubtables"), true, align);
+ if (has_pubschemas)
+ printTableAddHeader(&cont, gettext_noop("Pubschemas"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6007,8 +6072,13 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtables)
+ printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
+ if (has_pubschemas)
+ printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
- if (!puballtables)
+ /* Prior to version 14 check was based on all tables */
+ if ((has_pubtables && pubtables) || (!has_pubtables && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6045,6 +6115,40 @@ describePublications(const char *pattern)
}
PQclear(tabres);
}
+ else if (pubschemas)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.prnspcid\n"
+ " AND ps.prpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+
+ tabres = PSQLexec(buf.data);
+ if (!tabres)
+ {
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
+ }
+ else
+ tables = PQntuples(tabres);
+
+ if (tables > 0)
+ printTableAddFooter(&cont, _("Schemas:"));
+
+ for (j = 0; j < tables; j++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(tabres, j, 0));
+
+ printTableAddFooter(&cont, buf.data);
+ }
+ PQclear(tabres);
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index f272e2c..11af0c8 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -131,6 +131,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 0dd50fe..da3bd01 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -20,6 +20,7 @@
#include "catalog/genbki.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
+#include "utils/array.h"
/* ----------------
* pg_publication definition. cpp turns this into
@@ -54,6 +55,12 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* indicates publication is for specific tables */
+ bool pubtables;
+
+ /* indicates publication is for specific schema tables */
+ bool pubschemas;
} FormData_pg_publication;
/* ----------------
@@ -83,6 +90,8 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ bool pubtables;
+ bool pubschemas;
} Publication;
extern Publication *GetPublication(Oid pubid);
@@ -106,15 +115,16 @@ typedef enum PublicationPartOpt
} PublicationPartOpt;
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetAllTablesPublicationRelations(Publication *publication);
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
-
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
-
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000..73d5815
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid prpubid; /* Oid of the publication */
+ Oid prnspcid; /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX(pg_publication_schema_oid_index, 8902, on pg_publication_schema using btree(oid oid_ops));
+#define PublicationSchemaObjectIndexId 8902
+DECLARE_UNIQUE_INDEX(pg_publication_schema_prrelid_prpubid_index, 8903, on pg_publication_schema using btree(prnspcid oid_ops, prpubid oid_ops));
+#define PublicationSchemaPrnspcidPrpubidIndexId 8903
+
+#endif /* PG_PUBLICATION_REL_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 00e2e62..581fedb 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -21,6 +21,7 @@
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index dc2bb40..8f9639e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1747,6 +1747,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3523,6 +3524,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3537,6 +3539,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348..1ba2952 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7..4052873 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | Tables | Schemas
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+--------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | f | f
+ testpub_default | regress_publication_user | f | f | t | f | 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 | Truncates | Via root | Tables | Schemas
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+--------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | f | f
+ testpub_default | regress_publication_user | f | t | t | t | f | f | f | f
(2 rows)
--- adding tables
@@ -85,10 +85,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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | t | t | t | f | f | f | f | f
(1 row)
DROP TABLE testpub_tbl2;
@@ -100,19 +100,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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | 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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | t | f
Tables:
"public.testpub_tbl3"
@@ -131,10 +131,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | t | f
Tables:
"public.testpub_parted"
@@ -147,10 +147,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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | t | t | f
Tables:
"public.testpub_parted"
@@ -170,10 +170,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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | t | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -211,10 +211,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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | f | f | t | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -258,10 +258,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | f | f | f | f
(1 row)
-- fail - must be owner of publication
@@ -271,20 +271,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 | Truncates | Via root | Tables | Schemas
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+--------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | 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 | Truncates | Via root | Tables | Schemas
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+--------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | f | f
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961..fe5a038 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
--
1.8.3.1
v3-0002-Tests-and-documentation-for-schema-level-support-.patchtext/x-patch; charset=US-ASCII; name=v3-0002-Tests-and-documentation-for-schema-level-support-.patchDownload
From e0bb91a040625100c2d2b3af4eba5c991eed6487 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh.c@enterprisedb.com>
Date: Sun, 31 Jan 2021 22:53:23 +0530
Subject: [PATCH v3 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/ref/alter_publication.sgml | 45 +++++-
doc/src/sgml/ref/create_publication.sgml | 31 ++++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 198 ++++++++++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 87 +++++++++++-
src/test/subscription/t/001_rep_changes.pl | 131 +++++++++++++++++-
7 files changed, 494 insertions(+), 7 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b..884cab4 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants change which schemas are part of the
+ publication. The <literal>SET SCHEMA</literal> clause will replace the list
+ of schemas in the publication with the specified one. The <literal>ADD
+ SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses will add and
+ remove one or more schemas from the publication. Note that adding schemas
+ to a publication that is already subscribed to will require a <literal>ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the subscribing side
+ in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -98,6 +112,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</varlistentry>
<varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
<para>
@@ -142,6 +165,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
+</programlisting></para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbc..09c079d 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -100,6 +101,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
<para>
@@ -222,6 +233,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+production schema:
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+marketing and sales schemas:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a..49ea22f 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4052873..3349de7 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -255,7 +255,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -287,11 +286,208 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | f | f
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+RESET client_min_messages;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+RESET client_min_messages;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+--- Check create publication on a schema that does not exist.
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a object which is not schema.
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | f
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtables | Pubschemas
+--------------------------+------------+---------+---------+---------+-----------+----------+-----------+------------
+ regress_publication_user | f | t | t | t | t | f | f | t
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e..56d9b85 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075..ae23f9f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,97 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+RESET client_min_messages;
+\dRp+ testpub1_forschema
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+RESET client_min_messages;
+\dRp+ testpub2_forschema
+
+--- Check create publication on a schema that does not exist.
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a object which is not schema.
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication.
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication.
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index c20fadc..c715340 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -3,7 +3,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 27;
+use Test::More tests => 40;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -246,6 +246,135 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.temp1 AS SELECT generate_series(1,10) AS a ");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.temp2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.temp1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.temp2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.temp1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.temp2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.temp1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.temp2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.temp1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.temp2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.temp1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.temp2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.temp1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.temp1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.temp1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.temp1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.temp3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.temp3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.temp3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.temp3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.temp3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.temp3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.temp3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.temp3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.temp2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
1.8.3.1
On Sun, Jan 31, 2021 at 11:32 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Jan 22, 2021 at 10:01 AM vignesh C <vignesh21@gmail.com> wrote:
Thanks Rahila for your comments. Please find my thoughts below:
On Wed, Jan 20, 2021 at 6:27 PM Rahila Syed <rahilasyed90@gmail.com> wrote:
Hi Vignesh,
I have handled the above scenario(drop schema should automatically
remove the schema entry from publication schema relation) & addition
of tests in the new v2 patch attached.
Thoughts?Please see some initial comments:
1. I think there should be more tests to show that the schema data is actually replicated
to the subscriber. Currently, I am not seeing the data being replicated when I use FOR SCHEMA.I will fix this issue and include more tests in my next version of the patch.
Modified to handle this and also added a few more tests.
2. How does replication behave when a table is added or removed from a subscribed schema
using ALTER TABLE SET SCHEMA?I would like to keep the behavior similar to the table behavior. I
will post more details for this along with my next version of the
patch.If a table is set to a different schema, after the schema change table
data will not be sent to the subscriber.
When a new table is added to the published schema, the table data will
be sent by the publisher, subscriber will not apply the changes. If
the change needs to be reflected, subscriber's publication should be
refreshed using "alter subscription mysub1 refresh publication". This
relation will be reflected in the subscriber relation when the
subscriber's publication is refreshed.
If a table is dropped, there is no impact on subscriber, This relation
will be present in pg_subscriber_rel after refreshing subscriber
publication.3. Can we have a default schema like a public or current schema that gets replicated in case the user didn't
specify one, this can be handy to replicate current schema tables.It looks like a good use case, I will check on the feasibility of this
and try to implement this.This can be done, I will handle this later.
4. + The fourth, fifth and sixth variants change which schemas are part of the + publication. The <literal>SET TABLE</literal> clause will replace the list + of schemas in the publication with the specified one. The <literal>ADDThere is a typo above s/SET TABLE/SET SCHEMA
I will fix this in the next version of the patch.
Modified it.
I have separated the tests and documentation into a separate patch to
make review easier. Attached v3 patch with the fixes.
Thoughts?
The earlier patch does not apply on the head. The v4 patch attached
has the following changes:
a) Rebased it on head. b) Removed pubschemas, pubtables columns and
replaced it with pubtype in pg_publication table. c) List the schemas
in describe publication. d) List the publication in list schemas. e)
Add support for "FOR SCHEMA CURRENT_SCHEMA". f) Tab completion for
"FOR SCHEMA" in create publication and alter publication. g) Included
the newly added structure type to typedefs.lst
Regards,
Vignesh
Attachments:
v4-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v4-0001-Added-schema-level-support-for-publication.patchDownload
From 3dc9cb394278cd26681b128a937cdb034209aff7 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 10 May 2021 14:53:58 +0530
Subject: [PATCH v4 1/2] Added schema level support for publication.
This patch adds schema level support for publication. User can specify multiple
schemas with schema option. When user specifies schema option, then the tables
present in the schema specified will be selected by publisher for sending the
data to subscriber.
pg_publication maintains the information about the publication. puballtables
bool column was used to indicate if the publication is "FOR ALL TABLES" or
"FOR TABLE" type currently. With the introduction of "FOR SCHEMA" publication
type, it is not easy to determine the publication type, hence a new column
pubtype was added to pg_publication relation to indicate the publication type.
There is a possibility to do without addition of new column, but that will
require checking puballtables of pg_publication and checking pg_publication_rel
for table type publication and then checking pg_publication_schema for schema
type publication. I preferred to use introduce pubtype which makes things
easier, this also will help for supporting new options in the future. New
system table pg_publication_schema was added which will maintain the schemas
that user wanted to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schema to the publication/publication_schema
when the schema is renamed/dropped. Decoder identifies if the relation is part
of the publication and replicates it to the subscriber. Changes was done in
pg_dump to handle pubtype updation in pg_publication table while the database
gets upgraded.
Prototypes present in pg_publication.h was moved to publicationcmds.h so
that minimal datastructures can be exported to pg_dump and psql clients and the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing as this feature
involves catalog change.
TODO: version checks for psql/pg_dump need to be changed from 140000 to 150000
once the ongoing release is completed.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 147 ++++++++++
src/backend/catalog/pg_publication.c | 165 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/lockcmds.c | 1 +
src/backend/commands/publicationcmds.c | 296 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 16 +-
src/backend/utils/cache/relcache.c | 1 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 165 ++++++++++-
src/bin/pg_dump/pg_dump.h | 16 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 159 ++++++++++-
src/bin/psql/tab-complete.c | 21 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 41 +--
src/include/catalog/pg_publication_schema.h | 49 ++++
src/include/commands/publicationcmds.h | 21 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/syscache.h | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 3 +
32 files changed, 1282 insertions(+), 123 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 69f9dd51a7..30026a967b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -67,8 +67,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 53392414f1..59600fc98d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3433,6 +3433,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3572,6 +3573,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0c37fc1d53..c0a9fb0c7e 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -179,6 +180,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1470,6 +1472,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2863,6 +2869,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d79c3cde7c..5907081636 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,51 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+ return address;
+ }
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2265,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2358,9 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ objnode = (Node *) list_make2(linitial(name), linitial(args));
+ break;
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3964,45 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4577,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5712,6 +5817,48 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ if (objname)
+ *objname = list_make1(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 86e415af89..9f76642d8d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,76 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_rel];
+ bool nulls[Natts_pg_publication_rel];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +377,45 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +454,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +524,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets list of all relation published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Publication *publication)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(publication->oid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(publication->pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +574,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +674,16 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication);
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 5bde507c75..4673e8e39d 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2120,6 +2122,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2202,6 +2205,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270ced..3732f3727d 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -17,6 +17,7 @@
#include "access/table.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c8e0..e318e5a245 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(List *options,
@@ -141,6 +146,52 @@ parse_publication_options(List *options,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemoid;
+
+ switch (schema->schematype)
+ {
+ List *search_path;
+ char *nspname;
+
+ case SCHEMASPEC_CURRENT_SCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ nspname = get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemoid = get_namespace_oid(nspname, false);
+ break;
+
+ default:
+ schemoid = get_namespace_oid(schema->schemaname, false);
+ break;
+ }
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -213,6 +264,15 @@ CreatePublication(CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -226,6 +286,20 @@ CreatePublication(CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -252,6 +326,32 @@ CreatePublication(CreatePublicationStmt *stmt)
return myself;
}
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -311,7 +411,7 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
@@ -363,19 +463,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -427,11 +539,90 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, schemaoidlist)
+ {
+ Oid newschemaid = lfirst_oid(newlc);
+
+ if (newschemaid == oldschemaid)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +651,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -498,6 +691,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -608,7 +825,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -632,6 +849,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -666,6 +916,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -697,7 +981,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 6906714298..b108b641c5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 11e91c4ad3..2f9a72a356 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12150,6 +12151,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index aaf1a51f68..6fa12b6e77 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9605,45 +9607,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9655,6 +9680,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9689,6 +9719,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16635,6 +16689,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/* makeSchemaSpec
+ * Create a SchemaSpec with the given type
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index f68348dcf4..e1ef746c0f 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -16,6 +16,7 @@
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1042,13 +1043,26 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ List *pubschemas = GetPublicationSchemas(pub->oid);
+
+ if (list_member_oid(pubschemas, schemaId))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
+
if (!publish)
{
bool ancestor_published = false;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615e76..d4a6e3f711 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e4dc4ee34e..7cd7fd2a36 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 86de26a4bf..c60fe8cf44 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2815,7 +2815,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 339c393718..e0820e728e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -50,6 +50,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_trigger_d.h"
#include "catalog/pg_type_d.h"
#include "common/connect.h"
@@ -4013,6 +4014,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -4027,25 +4029,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 140000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4063,6 +4077,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4087,6 +4102,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4129,7 +4145,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4196,6 +4212,101 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 140000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the table. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not member of any publications. Clean up and return.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4283,6 +4394,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10387,6 +10536,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18585,6 +18737,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a09c..a9db477f25 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -613,6 +614,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -626,6 +628,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -731,6 +745,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 3e39fdb545..ebd95a3237 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3177,6 +3178,44 @@ describeOneTableDetails(const char *schemaname,
printTableAddFooter(&cont, buf.data);
}
PQclear(result);
+
+ /*
+ * Get the publications from "FOR SCHEMA" publications whose
+ * publication schema is same as table schema.
+ */
+ if (pset.sversion >= 140000)
+ {
+ int pub_schema_tuples;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE p.oid = ps.pspubid AND\n"
+ "ps.psnspcid = n.oid AND ps.pspubid = '%s'\n"
+ "AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (!tuples && pub_schema_tuples > 0)
+ printTableAddFooter(&cont, _("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ printTableAddFooter(&cont, buf.data);
+ }
+ PQclear(result);
+ }
}
}
@@ -5021,6 +5060,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5102,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 140000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for storing
+ * NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup("Publications:");
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6245,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6280,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6224,6 +6326,7 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
if (pset.sversion < 100000)
{
@@ -6237,6 +6340,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 140000);
initPQExpBuffer(&buf);
@@ -6250,6 +6354,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6292,6 +6400,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ char pubtype;
int j;
PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
@@ -6301,6 +6410,8 @@ describePublications(const char *pattern)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6426,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6438,16 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 14 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6363,6 +6484,40 @@ describePublications(const char *pattern)
}
PQclear(tabres);
}
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+
+ tabres = PSQLexec(buf.data);
+ if (!tabres)
+ {
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
+ }
+ else
+ tables = PQntuples(tabres);
+
+ if (tables > 0)
+ printTableAddFooter(&cont, _("Schemas:"));
+
+ for (j = 0; j < tables; j++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(tabres, j, 0));
+
+ printTableAddFooter(&cont, buf.data);
+ }
+ PQclear(tabres);
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6598c5369a..929d45d869 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish");
@@ -2630,12 +2639,16 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish");
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fd44081e74..08ec4c79f1 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -131,6 +131,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 1b31fee9e3..f67f92f918 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -83,12 +85,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -105,16 +104,26 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype,"a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if(strcmp(strpubtype,"t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype,"s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..e3e2f2d0ed
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid; /* Oid of the publication */
+ Oid psnspcid; /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, on pg_publication_schema using btree(oid oid_ops));
+#define PublicationSchemaObjectIndexId 8902
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+#define PublicationSchemaPsnspcidPspubidIndexId 8903
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 00e2e626e6..3c2a77d0b0 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,34 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(Publication *publication);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..dfeade5bf8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342019..b67e20ddc1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3612,6 +3630,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3626,6 +3645,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..28bf8daa64 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -85,10 +85,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -100,19 +100,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -131,10 +131,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -147,10 +147,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -170,10 +170,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -211,10 +211,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -258,10 +258,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -271,20 +271,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..fe5a038824 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index abdb08319c..b4d1c81898 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2031,6 +2031,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2322,6 +2323,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v4-0002-Tests-and-documentation-for-schema-level-support-.patchtext/x-patch; charset=US-ASCII; name=v4-0002-Tests-and-documentation-for-schema-level-support-.patchDownload
From d7945d96b79ef2752084459282553380cacd4129 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Sat, 22 May 2021 17:49:42 +0530
Subject: [PATCH v4 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 +++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 287 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 111 ++++++-
src/test/subscription/t/001_rep_changes.pl | 137 ++++++++-
8 files changed, 711 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 8aebc4d12f..8f9f2556ba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6168,6 +6173,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> and
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-altersubscription">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respctively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6235,6 +6262,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11280,9 +11368,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal> FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..884cab47b9 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants change which schemas are part of the
+ publication. The <literal>SET SCHEMA</literal> clause will replace the list
+ of schemas in the publication with the specified one. The <literal>ADD
+ SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses will add and
+ remove one or more schemas from the publication. Note that adding schemas
+ to a publication that is already subscribed to will require a <literal>ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the subscribing side
+ in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..a7d3a34194 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+production schema:
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+marketing and sales schemas:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 28bf8daa64..6e734a144d 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -255,7 +255,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -287,11 +286,297 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+\dn pub_test1;
+ List of schemas
+ Name | Owner
+-----------+--------------------------
+ pub_test1 | regress_publication_user
+Publications:
+ "testpub1_forschema"
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+\dn pub_test1;
+ List of schemas
+ Name | Owner
+-----------+--------------------------
+ pub_test1 | regress_publication_user
+Publications:
+ "testpub1_forschema"
+ "testpub2_forschema"
+
+\dn pub_test2;
+ List of schemas
+ Name | Owner
+-----------+--------------------------
+ pub_test2 | regress_publication_user
+Publications:
+ "testpub2_forschema"
+
+\dn pub_test3;
+ List of schemas
+ Name | Owner
+-----------+--------------------------
+ pub_test3 | regress_publication_user
+Publications:
+ "testpub2_forschema"
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+\dn public;
+ List of schemas
+ Name | Owner
+--------+---------
+ public | vignesh
+Publications:
+ "testpub3_forschema"
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist.
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a object which is not schema.
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..6a164ac0d4 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,121 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+\dn pub_test1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+\dn pub_test1;
+\dn pub_test2;
+\dn pub_test3;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+\dn public;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist.
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a object which is not schema.
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication.
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication.
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 1f654ee6af..a78416827b 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 28;
+use Test::More tests => 41;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -253,6 +253,141 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a ");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Monday, May 24, 2021 at 8:31 PM vignesh C <vignesh21@gmail.com> wrote:
The earlier patch does not apply on the head. The v4 patch attached
has the following changes:
a) Rebased it on head. b) Removed pubschemas, pubtables columns and
replaced it with pubtype in pg_publication table. c) List the schemas
in describe publication. d) List the publication in list schemas. e)
Add support for "FOR SCHEMA CURRENT_SCHEMA". f) Tab completion for
"FOR SCHEMA" in create publication and alter publication. g) Included
the newly added structure type to typedefs.lst
Thanks for your patch.
I ran "make check-world" after applying your patch but it failed on my machine. I saw the following log:
--------------
parallel group (2 tests): subscription publication
publication ... FAILED 108 ms
subscription ... ok 87 ms
diff -U3 /home/fnst/data/postgresql_schema/postgresql/src/test/regress/expected/publication.out /home/fnst/data/postgresql_schema/postgresql/src/test/regress/results/publication.out
--- /home/fnst/data/postgresql_schema/postgresql/src/test/regress/expected/publication.out 2021-05-25 15:44:52.261683712 +0800
+++ /home/fnst/data/postgresql_schema/postgresql/src/test/regress/results/publication.out 2021-05-25 15:48:41.393672595 +0800
@@ -359,10 +359,10 @@
"public"
\dn public;
- List of schemas
- Name | Owner
---------+---------
- public | vignesh
+List of schemas
+ Name | Owner
+--------+-------
+ public | fnst
Publications:
"testpub3_forschema"
--------------
I think the owner of CURRENT_SCHEMA should not be written into publication.out because the result is related to the user.
Maybe we can use "ALTER SCHEMA public OWNER TO owner" to change its default owner before this test case. Thoughts?
Regards
Tang
On Tue, May 25, 2021 at 2:47 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Monday, May 24, 2021 at 8:31 PM vignesh C <vignesh21@gmail.com> wrote:
The earlier patch does not apply on the head. The v4 patch attached
has the following changes:
a) Rebased it on head. b) Removed pubschemas, pubtables columns and
replaced it with pubtype in pg_publication table. c) List the schemas
in describe publication. d) List the publication in list schemas. e)
Add support for "FOR SCHEMA CURRENT_SCHEMA". f) Tab completion for
"FOR SCHEMA" in create publication and alter publication. g) Included
the newly added structure type to typedefs.lstThanks for your patch.
I ran "make check-world" after applying your patch but it failed on my machine. I saw the following log:
--------------
parallel group (2 tests): subscription publication
publication ... FAILED 108 ms
subscription ... ok 87 msdiff -U3 /home/fnst/data/postgresql_schema/postgresql/src/test/regress/expected/publication.out /home/fnst/data/postgresql_schema/postgresql/src/test/regress/results/publication.out --- /home/fnst/data/postgresql_schema/postgresql/src/test/regress/expected/publication.out 2021-05-25 15:44:52.261683712 +0800 +++ /home/fnst/data/postgresql_schema/postgresql/src/test/regress/results/publication.out 2021-05-25 15:48:41.393672595 +0800 @@ -359,10 +359,10 @@ "public"\dn public; - List of schemas - Name | Owner ---------+--------- - public | vignesh +List of schemas + Name | Owner +--------+------- + public | fnst Publications: "testpub3_forschema" --------------I think the owner of CURRENT_SCHEMA should not be written into publication.out because the result is related to the user.
Maybe we can use "ALTER SCHEMA public OWNER TO owner" to change its default owner before this test case. Thoughts?
Thanks for identifying and reporting this issue. I have \dn with the
equivalent query to display only the publication name. The updated
patch has the fix for the same.
Regards,
Vignesh
Attachments:
v5-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v5-0001-Added-schema-level-support-for-publication.patchDownload
From 5880d7209d8e3dea6203e239581d5c0444f70863 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 10 May 2021 14:53:58 +0530
Subject: [PATCH v5 1/2] Added schema level support for publication.
This patch adds schema level support for publication. User can specify multiple
schemas with schema option. When user specifies schema option, then the tables
present in the schema specified will be selected by publisher for sending the
data to subscriber.
pg_publication maintains the information about the publication. puballtables
bool column was used to indicate if the publication is "FOR ALL TABLES" or
"FOR TABLE" type currently. With the introduction of "FOR SCHEMA" publication
type, it is not easy to determine the publication type, hence a new column
pubtype was added to pg_publication relation to indicate the publication type.
There is a possibility to do without addition of new column, but that will
require checking puballtables of pg_publication and checking pg_publication_rel
for table type publication and then checking pg_publication_schema for schema
type publication. I preferred to use introduce pubtype which makes things
easier, this also will help for supporting new options in the future. New
system table pg_publication_schema was added which will maintain the schemas
that user wanted to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schema to the publication/publication_schema
when the schema is renamed/dropped. Decoder identifies if the relation is part
of the publication and replicates it to the subscriber. Changes was done in
pg_dump to handle pubtype updation in pg_publication table while the database
gets upgraded.
Prototypes present in pg_publication.h was moved to publicationcmds.h so
that minimal datastructures can be exported to pg_dump and psql clients and the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing as this feature
involves catalog change.
TODO: version checks for psql/pg_dump need to be changed from 140000 to 150000
once the ongoing release is completed.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 147 ++++++++++
src/backend/catalog/pg_publication.c | 165 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/lockcmds.c | 1 +
src/backend/commands/publicationcmds.c | 296 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 122 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 16 +-
src/backend/utils/cache/relcache.c | 1 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 165 ++++++++++-
src/bin/pg_dump/pg_dump.h | 16 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 159 ++++++++++-
src/bin/psql/tab-complete.c | 28 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 41 +--
src/include/catalog/pg_publication_schema.h | 49 ++++
src/include/commands/publicationcmds.h | 21 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 3 +
33 files changed, 1290 insertions(+), 126 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 69f9dd51a7..30026a967b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -67,8 +67,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 53392414f1..59600fc98d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3433,6 +3433,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3572,6 +3573,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0c37fc1d53..c0a9fb0c7e 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -179,6 +180,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1470,6 +1472,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2863,6 +2869,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d79c3cde7c..5907081636 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,51 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+ return address;
+ }
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2265,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2358,9 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ objnode = (Node *) list_make2(linitial(name), linitial(args));
+ break;
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3964,45 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4577,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5712,6 +5817,48 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ if (objname)
+ *objname = list_make1(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 86e415af89..9f76642d8d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,76 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_rel];
+ bool nulls[Natts_pg_publication_rel];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +377,45 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +454,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +524,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets list of all relation published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Publication *publication)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(publication->oid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(publication->pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +574,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +674,16 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication);
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 5bde507c75..4673e8e39d 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2120,6 +2122,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2202,6 +2205,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270ced..3732f3727d 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -17,6 +17,7 @@
#include "access/table.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c8e0..5fe4b1ad6f 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(List *options,
@@ -141,6 +146,52 @@ parse_publication_options(List *options,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemoid;
+
+ switch (schema->schematype)
+ {
+ List *search_path;
+ char *nspname;
+
+ case SCHEMASPEC_CURRENT_SCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ nspname = get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemoid = get_namespace_oid(nspname, false);
+ break;
+
+ default:
+ schemoid = get_namespace_oid(schema->schemaname, false);
+ break;
+ }
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -213,6 +264,15 @@ CreatePublication(CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -226,6 +286,20 @@ CreatePublication(CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -252,6 +326,32 @@ CreatePublication(CreatePublicationStmt *stmt)
return myself;
}
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -311,7 +411,7 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
@@ -363,19 +463,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -427,11 +539,90 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, schemaoidlist)
+ {
+ Oid newschemaid = lfirst_oid(newlc);
+
+ if (newschemaid == oldschemaid)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +651,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -498,6 +691,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -608,7 +825,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -632,6 +849,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -666,6 +916,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -697,7 +981,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 6906714298..b108b641c5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac46b..d57cc0bcba 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12139,6 +12140,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ee90e3f13..95b1d497ec 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9607,45 +9609,70 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ | /*EMPTY*/
+ { $$ = list_make1(makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, -1));}
+ ;
/*****************************************************************************
*
@@ -9657,6 +9684,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9691,6 +9723,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16637,6 +16693,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/* makeSchemaSpec
+ * Create a SchemaSpec with the given type
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index fe12d08a94..400155ad82 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -16,6 +16,7 @@
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1060,13 +1061,26 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ List *pubschemas = GetPublicationSchemas(pub->oid);
+
+ if (list_member_oid(pubschemas, schemaId))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
+
if (!publish)
{
bool ancestor_published = false;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615e76..d4a6e3f711 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e4dc4ee34e..b2f8b8add8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 6b046e7734..cb65dc32ed 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7c3b..dd049da209 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -50,6 +50,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_trigger_d.h"
#include "catalog/pg_type_d.h"
#include "common/connect.h"
@@ -3958,6 +3959,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3972,25 +3974,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 140000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4008,6 +4022,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4032,6 +4047,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4074,7 +4090,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4141,6 +4157,101 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 140000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the table. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not member of any publications. Clean up and return.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4228,6 +4339,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10332,6 +10481,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18528,6 +18680,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a09c..a9db477f25 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -613,6 +614,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -626,6 +628,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -731,6 +745,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2abf255798..004e2d24d4 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3177,6 +3178,44 @@ describeOneTableDetails(const char *schemaname,
printTableAddFooter(&cont, buf.data);
}
PQclear(result);
+
+ /*
+ * Get the publications from "FOR SCHEMA" publications whose
+ * publication schema is same as table schema.
+ */
+ if (pset.sversion >= 140000)
+ {
+ int pub_schema_tuples;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE p.oid = ps.pspubid AND\n"
+ "ps.psnspcid = n.oid AND ps.pspubid = '%s'\n"
+ "AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (!tuples && pub_schema_tuples > 0)
+ printTableAddFooter(&cont, _("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ printTableAddFooter(&cont, buf.data);
+ }
+ PQclear(result);
+ }
}
}
@@ -5021,6 +5060,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5102,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 140000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for storing
+ * NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup("Publications:");
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6245,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6280,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6224,6 +6326,7 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
if (pset.sversion < 100000)
{
@@ -6237,6 +6340,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 140000);
initPQExpBuffer(&buf);
@@ -6250,6 +6354,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6292,6 +6400,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ char pubtype;
int j;
PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
@@ -6301,6 +6410,8 @@ describePublications(const char *pattern)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6426,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6438,16 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 14 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6363,6 +6484,40 @@ describePublications(const char *pattern)
}
PQclear(tabres);
}
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+
+ tabres = PSQLexec(buf.data);
+ if (!tabres)
+ {
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
+ }
+ else
+ tables = PQntuples(tabres);
+
+ if (tables > 0)
+ printTableAddFooter(&cont, _("Schemas:"));
+
+ for (j = 0; j < tables; j++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(tabres, j, 0));
+
+ printTableAddFooter(&cont, buf.data);
+ }
+ PQclear(tabres);
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 109b22acb6..27bf908d9d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish");
@@ -2630,15 +2639,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
- /* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
- else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish");
+ /* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fd44081e74..08ec4c79f1 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -131,6 +131,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 1b31fee9e3..f67f92f918 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -83,12 +85,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -105,16 +104,26 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype,"a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if(strcmp(strpubtype,"t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype,"s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..b0c9361d91
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, on pg_publication_schema using btree(oid oid_ops));
+#define PublicationSchemaObjectIndexId 8902
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+#define PublicationSchemaPsnspcidPspubidIndexId 8903
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 00e2e626e6..3c2a77d0b0 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,34 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(Publication *publication);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..dfeade5bf8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342019..b67e20ddc1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3612,6 +3630,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3626,6 +3645,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..28bf8daa64 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -85,10 +85,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -100,19 +100,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -131,10 +131,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -147,10 +147,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -170,10 +170,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -211,10 +211,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -258,10 +258,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -271,20 +271,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..fe5a038824 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index abdb08319c..b4d1c81898 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2031,6 +2031,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2322,6 +2323,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v5-0002-Tests-and-documentation-for-schema-level-support-.patchtext/x-patch; charset=US-ASCII; name=v5-0002-Tests-and-documentation-for-schema-level-support-.patchDownload
From 63274e47c4c1de70f9eaf5b4d242fed4a7c526db Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Sat, 22 May 2021 17:49:42 +0530
Subject: [PATCH v5 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 +++++-
doc/src/sgml/ref/alter_publication.sgml | 46 ++-
doc/src/sgml/ref/create_publication.sgml | 45 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 305 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 122 +++++++-
src/test/subscription/t/001_rep_changes.pl | 142 ++++++++-
8 files changed, 747 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 16493209c6..f2039c7e1b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6172,6 +6177,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> and
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-altersubscription">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respctively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6239,6 +6266,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11275,9 +11363,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal> FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..26b8674bc0 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA [ { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] ]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA [ { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] ]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA [ { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] ]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants change which schemas are part of the
+ publication. The <literal>SET SCHEMA</literal> clause will replace the list
+ of schemas in the publication with the specified one. The <literal>ADD
+ SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses will add and
+ remove one or more schemas from the publication. Note that adding schemas
+ to a publication that is already subscribed to will require a <literal>ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the subscribing side
+ in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema. If schema is not specified, publication will
+ be created for CURRENT_SCHEMA.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +165,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..357ea553a6 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA [ { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ] ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,17 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future. If
+ schema is not specified, publication will be created for CURRENT_SCHEMA.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +165,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +183,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +235,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+production schema:
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+marketing and sales schemas:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 28bf8daa64..38b2e0a5de 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -255,7 +255,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -287,11 +286,315 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication without schema name
+CREATE PUBLICATION testpub4_forschema FOR SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+ testpub4_forschema
+(2 rows)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+-- Alter publication set it without specifying schema_name, it should set to CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..1c50bbdb03 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,132 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication without schema name
+CREATE PUBLICATION testpub4_forschema FOR SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it without specifying schema_name, it should set to CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 1f654ee6af..3cb75dd27e 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 28;
+use Test::More tests => 41;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -253,6 +253,146 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a ");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Sat, Jun 5, 2021 at 7:02 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for identifying and reporting this issue. I have \dn with the
equivalent query to display only the publication name. The updated
patch has the fix for the same.
Currently, FOR ALL TABLES is there to add all the tables(existing and
future) in the current database in which the publication is created. I
wonder before providing FOR SCHEMA capability, we better target FOR
DATABASE first, something like CREATE PUBLICATION ... FOR DATABASE
foo, bar, baz, qux; Of course users with the proper permissions on the
specified databases can add them to the publication. This can help to
add all the tables in other databases as well. Then, the CREATE
PUBLICATION ... FOR SCHEMA foo, bar, baz, qux; makes more sense.
Because, my understanding is that: database is a collection of tables,
schema is a collection of databases. I may be wrong here, but it's
just a thought. What do you think?
With Regards,
Bharath Rupireddy.
On Fri, Jun 11, 2021, 6:22 PM Bharath Rupireddy <
bharath.rupireddyforpostgres@gmail.com> wrote:
On Sat, Jun 5, 2021 at 7:02 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for identifying and reporting this issue. I have \dn with the
equivalent query to display only the publication name. The updated
patch has the fix for the same.Currently, FOR ALL TABLES is there to add all the tables(existing and
future) in the current database in which the publication is created. I
wonder before providing FOR SCHEMA capability, we better target FOR
DATABASE first, something like CREATE PUBLICATION ... FOR DATABASE
foo, bar, baz, qux; Of course users with the proper permissions on the
specified databases can add them to the publication. This can help to
add all the tables in other databases as well. Then, the CREATE
PUBLICATION ... FOR SCHEMA foo, bar, baz, qux; makes more sense.
Because, my understanding is that: database is a collection of tables,
schema is a collection of databases. I may be wrong here, but it's
just a thought. What do you think?
Please ignore above comment. I was confused about what a database and
schema is in postgres. I'm sorry for the noise.
https://www.postgresql.org/docs/devel/ddl-schemas.html
Regards,
Bharath Rupireddy.
On Sat, Jan 9, 2021 at 8:08 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
And, with this feature, since there can be many huge tables inside a
schema, the initial table sync phase of the replication can take a
while.Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?
Isn't this applies to FOR ALL TABLES syntax as well where the user
might want to exclude one or a few tables? I am not sure if it is a
good idea to deal with this as part of this patch.
--
With Regards,
Amit Kapila.
On Sat, Jun 5, 2021 at 11:32 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for identifying and reporting this issue. I have \dn with the
equivalent query to display only the publication name. The updated
patch has the fix for the same.
The patch no longer applies, I think a recent change to tab-complete
has broken it.
regards,
Ajin Cherian
Fujitsu Australia
On Wed, Jun 16, 2021 at 5:12 PM Ajin Cherian <itsajin@gmail.com> wrote:
On Sat, Jun 5, 2021 at 11:32 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for identifying and reporting this issue. I have \dn with the
equivalent query to display only the publication name. The updated
patch has the fix for the same.The patch no longer applies, I think a recent change to tab-complete
has broken it.
Thanks for reporting it, the attached patch is a rebased version of
the patch with few review comment fixes, I will reply with the comment
fixes to the respective mails.
Regards,
Vignesh
Attachments:
v6-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v6-0001-Added-schema-level-support-for-publication.patchDownload
From 3f5a53395edc950d6b8d8bef4e0b0a5a7e9e4f83 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 14 Jun 2021 10:23:23 +0530
Subject: [PATCH v6 1/4] Added schema level support for publication.
This patch adds schema level support for publication. User can specify multiple
schemas with schema option. When user specifies schema option, then the tables
present in the schema specified will be selected by publisher for sending the
data to subscriber.
pg_publication maintains the information about the publication. puballtables
bool column was used to indicate if the publication is "FOR ALL TABLES" or
"FOR TABLE" type currently. With the introduction of "FOR SCHEMA" publication
type, it is not easy to determine the publication type, hence a new column
pubtype was added to pg_publication relation to indicate the publication type.
There is a possibility to do without addition of new column, but that will
require checking puballtables of pg_publication and checking pg_publication_rel
for table type publication and then checking pg_publication_schema for schema
type publication. I preferred to use introduce pubtype which makes things
easier, this also will help for supporting new options in the future. New
system table pg_publication_schema was added which will maintain the schemas
that user wanted to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schema to the publication/publication_schema
when the schema is renamed/dropped. Decoder identifies if the relation is part
of the publication and replicates it to the subscriber. Changes was done in
pg_dump to handle pubtype updation in pg_publication table while the database
gets upgraded.
Prototypes present in pg_publication.h was moved to publicationcmds.h so
that minimal datastructures can be exported to pg_dump and psql clients and the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing as this feature
involves catalog change.
TODO: version checks for psql/pg_dump need to be changed from 140000 to 150000
once the ongoing release is completed.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 147 ++++++++++
src/backend/catalog/pg_publication.c | 165 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/lockcmds.c | 1 +
src/backend/commands/publicationcmds.c | 296 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 16 +-
src/backend/utils/cache/relcache.c | 1 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 165 ++++++++++-
src/bin/pg_dump/pg_dump.h | 16 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 218 +++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 41 +--
src/include/catalog/pg_publication_schema.h | 49 ++++
src/include/commands/publicationcmds.h | 21 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 3 +
33 files changed, 1303 insertions(+), 164 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 69f9dd51a7..30026a967b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -67,8 +67,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 53392414f1..59600fc98d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3433,6 +3433,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3572,6 +3573,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0c37fc1d53..c0a9fb0c7e 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -179,6 +180,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1470,6 +1472,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2863,6 +2869,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..cae0b3b7e6 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,51 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+ return address;
+ }
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2265,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2358,9 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ objnode = (Node *) list_make2(linitial(name), linitial(args));
+ break;
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3964,45 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4577,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5816,48 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ if (objname)
+ *objname = list_make1(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 86e415af89..41243790d6 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,76 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +377,45 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +454,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +524,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets list of all relation published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Publication *publication)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(publication->oid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(publication->pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +574,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +674,16 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication);
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..34cf049632 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270ced..3732f3727d 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -17,6 +17,7 @@
#include "access/table.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c8e0..5fe4b1ad6f 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(List *options,
@@ -141,6 +146,52 @@ parse_publication_options(List *options,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemoid;
+
+ switch (schema->schematype)
+ {
+ List *search_path;
+ char *nspname;
+
+ case SCHEMASPEC_CURRENT_SCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ nspname = get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemoid = get_namespace_oid(nspname, false);
+ break;
+
+ default:
+ schemoid = get_namespace_oid(schema->schemaname, false);
+ break;
+ }
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -213,6 +264,15 @@ CreatePublication(CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -226,6 +286,20 @@ CreatePublication(CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -252,6 +326,32 @@ CreatePublication(CreatePublicationStmt *stmt)
return myself;
}
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -311,7 +411,7 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
@@ -363,19 +463,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -427,11 +539,90 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, schemaoidlist)
+ {
+ Oid newschemaid = lfirst_oid(newlc);
+
+ if (newschemaid == oldschemaid)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +651,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -498,6 +691,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -608,7 +825,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -632,6 +849,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -666,6 +916,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -697,7 +981,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 6906714298..b108b641c5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac46b..d57cc0bcba 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12139,6 +12140,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb24195438..81e508b7a5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/* makeSchemaSpec
+ * Create a SchemaSpec with the given type
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 63f108f960..1057acd62e 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -16,6 +16,7 @@
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1063,13 +1064,26 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ List *pubschemas = GetPublicationSchemas(pub->oid);
+
+ if (list_member_oid(pubschemas, schemaId))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
+
if (!publish)
{
bool ancestor_published = false;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615e76..d4a6e3f711 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e4dc4ee34e..b2f8b8add8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 24cc096255..719d537497 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7c3b..dd049da209 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -50,6 +50,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_trigger_d.h"
#include "catalog/pg_type_d.h"
#include "common/connect.h"
@@ -3958,6 +3959,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3972,25 +3974,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 140000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4008,6 +4022,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4032,6 +4047,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4074,7 +4090,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4141,6 +4157,101 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 140000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the table. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not member of any publications. Clean up and return.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4228,6 +4339,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10332,6 +10481,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18528,6 +18680,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a09c..a9db477f25 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -613,6 +614,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -626,6 +628,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -731,6 +745,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2abf255798..73417cbfc4 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 14000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 140000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for storing
+ * NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup("Publications:");
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,39 @@ listPublications(const char *pattern)
return true;
}
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6344,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6360,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 140000);
initPQExpBuffer(&buf);
@@ -6250,6 +6374,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6415,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6441,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6453,16 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 14 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6473,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6499,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index bd8e9ea2f8..ba549e106e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2630,15 +2639,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fd44081e74..08ec4c79f1 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -131,6 +131,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 1b31fee9e3..f67f92f918 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -83,12 +85,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -105,16 +104,26 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype,"a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if(strcmp(strpubtype,"t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype,"s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..b0c9361d91
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, on pg_publication_schema using btree(oid oid_ops));
+#define PublicationSchemaObjectIndexId 8902
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+#define PublicationSchemaPsnspcidPspubidIndexId 8903
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 00e2e626e6..3c2a77d0b0 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,34 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(Publication *publication);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..dfeade5bf8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..169bdce07c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..28bf8daa64 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -85,10 +85,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -100,19 +100,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -131,10 +131,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -147,10 +147,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -170,10 +170,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -211,10 +211,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -258,10 +258,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -271,20 +271,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..fe5a038824 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index abdb08319c..b4d1c81898 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2031,6 +2031,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2322,6 +2323,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v6-0002-Tests-and-documentation-for-schema-level-support-.patchtext/x-patch; charset=US-ASCII; name=v6-0002-Tests-and-documentation-for-schema-level-support-.patchDownload
From e5a31ee9e339bf062e999c76450787b48703f573 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 14 Jun 2021 11:15:39 +0530
Subject: [PATCH v6 2/4] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 45 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 277 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 112 +++++++-
src/test/subscription/t/001_rep_changes.pl | 141 +++++++++-
8 files changed, 707 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f517a7d4af..b87d84a2e1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6171,6 +6176,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> and
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-altersubscription">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respctively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6238,6 +6265,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11274,9 +11362,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal> FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..532ca2ff62 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants change which schemas are part of the
+ publication. The <literal>SET SCHEMA</literal> clause will replace the list
+ of schemas in the publication with the specified one. The <literal>ADD
+ SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses will add and
+ remove one or more schemas from the publication. Note that adding schemas
+ to a publication that is already subscribed to will require a <literal>ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the subscribing side
+ in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..60fea8debe 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,17 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future. If
+ schema is not specified, publication will be created for CURRENT_SCHEMA.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +165,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +183,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +235,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+production schema:
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+marketing and sales schemas:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 28bf8daa64..bed28ef8a7 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -255,7 +255,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -287,11 +286,287 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..d55a6f42b3 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,122 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index ecfba0af04..36aa4393b7 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 31;
+use Test::More tests => 44;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -253,6 +253,145 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
v6-0003-Added-skip-table-option-to-skip-tables-for-all-ta.patchtext/x-patch; charset=US-ASCII; name=v6-0003-Added-skip-table-option-to-skip-tables-for-all-ta.patchDownload
From 423c9e47c0f06716db103b854549a7d7b777bf90 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Wed, 16 Jun 2021 18:05:27 +0530
Subject: [PATCH v6 3/4] Added skip-table option to skip tables for all
tables/for schema publication.
Added skip-table option to skip tables for all tables/for schema publication.
---
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 11 +-
src/backend/catalog/objectaddress.c | 98 +++++++++++++-
src/backend/catalog/pg_publication.c | 102 ++++++++++-----
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 129 +++++++++++++------
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 1 +
src/backend/parser/gram.y | 53 +++++++-
src/backend/replication/pgoutput/pgoutput.c | 37 +++---
src/backend/utils/cache/syscache.c | 23 ++++
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_dump.c | 41 ++++--
src/bin/pg_dump/pg_dump.h | 3 +-
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 25 +++-
src/bin/psql/tab-complete.c | 12 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 27 ++++
src/include/catalog/pg_publication_skiprel.h | 48 +++++++
src/include/commands/publicationcmds.h | 10 +-
src/include/nodes/parsenodes.h | 3 +
src/include/parser/kwlist.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
28 files changed, 521 insertions(+), 131 deletions(-)
create mode 100644 src/include/catalog/pg_publication_skiprel.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 30026a967b..1b51cc9545 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,7 +68,7 @@ CATALOG_HEADERS := \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
- pg_subscription.h pg_subscription_rel.h
+ pg_publication_skiprel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 59600fc98d..f5f01c500f 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3434,6 +3434,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
case OBJECT_PUBLICATION_SCHEMA:
+ case OBJECT_PUBLICATION_SKIP:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3574,6 +3575,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
case OBJECT_PUBLICATION_SCHEMA:
+ case OBJECT_PUBLICATION_SKIP:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index c0a9fb0c7e..de88f1b046 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_publication_schema.h"
+#include "catalog/pg_publication_skiprel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -181,6 +182,7 @@ static const Oid object_classes[] = {
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
+ PublicationSkipRelationId, /* OCLASS_PUBLICATION_SKIP */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1469,13 +1471,17 @@ doDeletion(const ObjectAddress *object, int flags)
break;
case OCLASS_PUBLICATION_REL:
- RemovePublicationRelById(object->objectId);
+ RemovePublicationRelById(object->objectId, false);
break;
case OCLASS_PUBLICATION_SCHEMA:
RemovePublicationSchemaById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SKIP:
+ RemovePublicationRelById(object->objectId, true);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2872,6 +2878,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationSchemaRelationId:
return OCLASS_PUBLICATION_SCHEMA;
+ case PublicationSkipRelationId:
+ return OCLASS_PUBLICATION_SKIP;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index cae0b3b7e6..6aebfbbe06 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -50,6 +50,7 @@
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_publication_schema.h"
+#include "catalog/pg_publication_skiprel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -835,6 +836,10 @@ static const struct object_type_map
{
"publication schema", OBJECT_PUBLICATION_SCHEMA
},
+ /* OCLASS_PUBLICATION_SKIPREL */
+ {
+ "publication skiprelation", OBJECT_PUBLICATION_SKIP
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -880,7 +885,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
bool missing_ok);
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
- bool missing_ok);
+ bool missing_ok,
+ bool is_skip);
static ObjectAddress get_object_address_publication_schema(List *object,
bool missing_ok);
@@ -1125,12 +1131,17 @@ get_object_address(ObjectType objtype, Node *object,
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
- missing_ok);
+ missing_ok, false);
break;
case OBJECT_PUBLICATION_SCHEMA:
address = get_object_address_publication_schema(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_SKIP:
+ address = get_object_address_publication_rel(castNode(List, object),
+ &relation,
+ missing_ok, true);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1900,16 +1911,19 @@ get_object_address_usermapping(List *object, bool missing_ok)
* publication name.
*/
static ObjectAddress
-get_object_address_publication_rel(List *object,
- Relation *relp, bool missing_ok)
+get_object_address_publication_rel(List *object, Relation *relp,
+ bool missing_ok, bool is_skip)
{
ObjectAddress address;
Relation relation;
List *relname;
char *pubname;
Publication *pub;
+ int relid = GET_PUBLICATION_REL_ID(is_skip);
+ int syscacheid = GET_PUBLICATION_MAP(is_skip);
+ int col_oid = GET_PUBLICATION_COL_OBJECTID(is_skip);
- ObjectAddressSet(address, PublicationRelRelationId, InvalidOid);
+ ObjectAddressSet(address, relid, InvalidOid);
relname = linitial(object);
relation = relation_openrv_extended(makeRangeVarFromNameList(relname),
@@ -1930,15 +1944,17 @@ get_object_address_publication_rel(List *object,
/* Find the publication relation mapping in syscache. */
address.objectId =
- GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
+ GetSysCacheOid2(syscacheid, col_oid,
ObjectIdGetDatum(RelationGetRelid(relation)),
ObjectIdGetDatum(pub->oid));
+
if (!OidIsValid(address.objectId))
{
if (!missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("publication relation \"%s\" in publication \"%s\" does not exist",
+ errmsg("publication %s \"%s\" in publication \"%s\" does not exist",
+ GET_PUBLICATION_REL_STR(is_skip),
RelationGetRelationName(relation), pubname)));
relation_close(relation, AccessShareLock);
return address;
@@ -2266,6 +2282,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
case OBJECT_PUBLICATION_SCHEMA:
+ case OBJECT_PUBLICATION_SKIP:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2361,6 +2378,9 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_SCHEMA:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
+ case OBJECT_PUBLICATION_SKIP:
+ objnode = (Node *) list_make2(name, linitial(args));
+ break;
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -4003,6 +4023,37 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SKIP:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_skip psform;
+ StringInfoData rel;
+
+ tup = SearchSysCache1(PUBLICATIONSKIP,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication table %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_skip) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+
+ initStringInfo(&rel);
+ getRelationDescription(&rel, psform->psrelid, false);
+
+ /* translator: first %s is, e.g., "table %s" */
+ appendStringInfo(&buffer, _("publication of %s in publication %s"),
+ rel.data, pubname);
+ pfree(rel.data);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4581,6 +4632,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication schema");
break;
+ case OCLASS_PUBLICATION_SKIP:
+ appendStringInfoString(&buffer, "publication skiprelation");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5858,6 +5913,35 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SKIP:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_skip psform;
+
+ tup = SearchSysCache1(PUBLICATIONSKIP,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication table %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_skip) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+
+ getRelationIdentity(&buffer, psform->psrelid, objname, false);
+ appendStringInfo(&buffer, " in publication %s", pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 41243790d6..05df5b43b0 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -32,6 +32,7 @@
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_publication_schema.h"
+#include "catalog/pg_publication_skiprel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
#include "funcapi.h"
@@ -145,26 +146,29 @@ pg_relation_is_publishable(PG_FUNCTION_ARGS)
*/
ObjectAddress
publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists)
+ bool if_not_exists, bool is_skip)
{
Relation rel;
HeapTuple tup;
- Datum values[Natts_pg_publication_rel];
- bool nulls[Natts_pg_publication_rel];
+ int ncolumns = GET_PUBLICATION_REL_COL_COUNT(is_skip);
+ Datum *values;
+ bool *nulls;
Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
+ int colid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
referenced;
- rel = table_open(PublicationRelRelationId, RowExclusiveLock);
+ rel = table_open(GET_PUBLICATION_REL_ID(is_skip), RowExclusiveLock);
/*
* Check for duplicates. Note that this does not really prevent
* duplicates, it's here just to provide nicer error message in common
* case. The real protection is the unique key on the catalog.
*/
- if (SearchSysCacheExists2(PUBLICATIONRELMAP, ObjectIdGetDatum(relid),
+ if (SearchSysCacheExists2(GET_PUBLICATION_MAP(is_skip),
+ ObjectIdGetDatum(relid),
ObjectIdGetDatum(pubid)))
{
table_close(rel, RowExclusiveLock);
@@ -174,23 +178,24 @@ publication_add_relation(Oid pubid, Relation targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
- errmsg("relation \"%s\" is already member of publication \"%s\"",
+ errmsg("%s \"%s\" is already member of publication \"%s\"",
+ GET_PUBLICATION_REL_STR(is_skip),
RelationGetRelationName(targetrel), pub->name)));
}
check_publication_add_relation(targetrel);
- /* Form a tuple. */
- memset(values, 0, sizeof(values));
- memset(nulls, false, sizeof(nulls));
+ values = (Datum *) palloc0(ncolumns * sizeof(Datum));
+ nulls = (bool *) palloc0(ncolumns * sizeof(bool));
- prrelid = GetNewOidWithIndex(rel, PublicationRelObjectIndexId,
- Anum_pg_publication_rel_oid);
- values[Anum_pg_publication_rel_oid - 1] = ObjectIdGetDatum(prrelid);
- values[Anum_pg_publication_rel_prpubid - 1] =
- ObjectIdGetDatum(pubid);
- values[Anum_pg_publication_rel_prrelid - 1] =
- ObjectIdGetDatum(relid);
+ colid = GET_PUBLICATION_COL_OBJECTID(is_skip);
+ prrelid = GetNewOidWithIndex(rel, PublicationRelObjectIndexId, colid);
+
+ values[colid - 1] = ObjectIdGetDatum(prrelid);
+ colid = GET_PUBLICATION_COL_PUBID(is_skip);
+ values[colid - 1] = ObjectIdGetDatum(pubid);
+ colid = GET_PUBLICATION_COL_RELID(is_skip);
+ values[colid - 1] = ObjectIdGetDatum(relid);
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
@@ -198,7 +203,7 @@ publication_add_relation(Oid pubid, Relation targetrel,
CatalogTupleInsert(rel, tup);
heap_freetuple(tup);
- ObjectAddressSet(myself, PublicationRelRelationId, prrelid);
+ ObjectAddressSet(myself, GET_PUBLICATION_REL_ID(is_skip), prrelid);
/* Add dependency on the publication */
ObjectAddressSet(referenced, PublicationRelationId, pubid);
@@ -318,7 +323,7 @@ GetRelationPublications(Oid relid)
* should use GetAllTablesPublicationRelations().
*/
List *
-GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt, bool is_skip)
{
List *result;
Relation pubrelsrel;
@@ -327,28 +332,38 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
HeapTuple tup;
/* Find all publications associated with the relation. */
- pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
+ pubrelsrel = table_open(GET_PUBLICATION_REL_ID(is_skip), AccessShareLock);
ScanKeyInit(&scankey,
- Anum_pg_publication_rel_prpubid,
+ GET_PUBLICATION_COL_PUBID(is_skip),
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(pubid));
- scan = systable_beginscan(pubrelsrel, PublicationRelPrrelidPrpubidIndexId,
+ scan = systable_beginscan(pubrelsrel,
+ GET_PUBLICATION_MULCOL_INDEXID(is_skip),
true, NULL, 1, &scankey);
result = NIL;
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
- Form_pg_publication_rel pubrel;
-
- pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+ Oid relid;
+ if (is_skip)
+ {
+ Form_pg_publication_skip pubskip;
+ pubskip = (Form_pg_publication_skip) GETSTRUCT(tup);
+ relid = pubskip->psrelid;
+ }
+ else
+ {
+ Form_pg_publication_rel pubrel;
+ pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+ relid = pubrel->prrelid;
+ }
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
pub_partopt != PUBLICATION_PART_ROOT)
{
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
+ List *all_parts = find_all_inheritors(relid, NoLock, NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
result = list_concat(result, all_parts);
@@ -368,7 +383,7 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(false);
}
else
- result = lappend_oid(result, pubrel->prrelid);
+ result = lappend_oid(result, relid);
}
systable_endscan(scan);
@@ -462,7 +477,8 @@ GetAllTablesPublications(void)
* in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid,
+ List *pubskiptablelist)
{
Relation classRel;
ScanKeyData key[2];
@@ -492,7 +508,8 @@ GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
Oid relid = relForm->oid;
if (is_publishable_class(relid, relForm) &&
- !(relForm->relispartition && pubviaroot))
+ !(relForm->relispartition && pubviaroot) &&
+ (!pubskiptablelist || !list_member_oid(pubskiptablelist, relid)))
result = lappend_oid(result, relid);
}
@@ -513,7 +530,8 @@ GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
Oid relid = relForm->oid;
if (is_publishable_class(relid, relForm) &&
- !relForm->relispartition)
+ !relForm->relispartition &&
+ (!pubskiptablelist || !list_member_oid(pubskiptablelist, relid)))
result = lappend_oid(result, relid);
}
@@ -532,6 +550,11 @@ GetAllSchemasPublicationRelations(Publication *publication)
{
List *result = NIL;
List *pubschemalist = GetPublicationSchemas(publication->oid);
+ List *pubskiptablelist = GetPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF,
+ true);
ListCell *cell;
foreach(cell, pubschemalist)
@@ -540,7 +563,8 @@ GetAllSchemasPublicationRelations(Publication *publication)
List *schemaRels = NIL;
schemaRels = GetAllTablesPublicationRelations(publication->pubviaroot,
- schemaOid);
+ schemaOid,
+ pubskiptablelist);
result = list_concat(result, schemaRels);
}
@@ -675,13 +699,23 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->pubtype == PUBTYPE_ALLTABLES)
+ {
+ List *pubskiptablelist;
+ pubskiptablelist = GetPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF,
+ true);
tables = GetAllTablesPublicationRelations(publication->pubviaroot,
- InvalidOid);
+ InvalidOid,
+ pubskiptablelist);
+ }
else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ false);
else if (publication->pubtype == PUBTYPE_SCHEMA)
tables = GetAllSchemasPublicationRelations(publication);
funcctx->user_fctx = (void *) tables;
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index e7c27459d8..0af1738f7a 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -662,6 +662,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
case OCLASS_PUBLICATION_SCHEMA:
+ case OCLASS_PUBLICATION_SKIP:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 34cf049632..b6e4b3c3e4 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -975,6 +975,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
case OBJECT_PUBLICATION_SCHEMA:
+ case OBJECT_PUBLICATION_SKIP:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1053,6 +1054,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
case OCLASS_PUBLICATION_SCHEMA:
+ case OCLASS_PUBLICATION_SKIP:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2134,6 +2136,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
case OBJECT_PUBLICATION_SCHEMA:
+ case OBJECT_PUBLICATION_SKIP:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2217,6 +2220,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
case OBJECT_PUBLICATION_SCHEMA:
+ case OBJECT_PUBLICATION_SKIP:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 5fe4b1ad6f..a4a1846f62 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -29,6 +29,7 @@
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_publication_schema.h"
+#include "catalog/pg_publication_skiprel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,8 +54,9 @@
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+ AlterPublicationStmt *stmt, bool is_skip);
+static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok,
+ bool is_skip);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -307,7 +309,18 @@ CreatePublication(CreatePublicationStmt *stmt)
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
+ PublicationAddTables(puboid, rels, true, NULL, false);
+ CloseTableList(rels);
+ }
+
+ if (stmt->skiptables)
+ {
+ List *rels;
+
+ Assert(list_length(stmt->skiptables) > 0);
+
+ rels = OpenTableList(stmt->skiptables);
+ PublicationAddTables(puboid, rels, true, NULL, true);
CloseTableList(rels);
}
@@ -423,7 +436,8 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
* trees, not just those explicitly mentioned in the publication.
*/
List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ PUBLICATION_PART_ALL,
+ false);
/*
* We don't want to send too many individual messages, at some point
@@ -456,44 +470,58 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
*/
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+ HeapTuple tup, bool is_skip)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
+ List *tables = is_skip ? stmt->skiptables : stmt->tables;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->pubtype == PUBTYPE_ALLTABLES)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (is_skip)
+ {
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Skip_Tables cannot be added to or dropped from FOR TABLE publications.")));
+ }
+ else
+ {
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
- if (pubform->pubtype == PUBTYPE_SCHEMA)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR SCHEMA",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+ }
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(tables) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(tables);
if (stmt->tableAction == DEFELEM_ADD)
{
- PublicationAddTables(pubid, rels, false, stmt);
- if (pubform->pubtype == PUBTYPE_EMPTY)
+ PublicationAddTables(pubid, rels, false, stmt, is_skip);
+ if (!is_skip && pubform->pubtype == PUBTYPE_EMPTY)
UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
PUBTYPE_TABLE);
}
else if (stmt->tableAction == DEFELEM_DROP)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropTables(pubid, rels, false, is_skip);
else /* DEFELEM_SET */
{
List *oldrelids = GetPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
+ PUBLICATION_PART_ROOT,
+ is_skip);
List *delrels = NIL;
ListCell *oldlc;
@@ -525,13 +553,13 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
}
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropTables(pubid, delrels, true, is_skip);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddTables(pubid, rels, true, stmt, is_skip);
CloseTableList(delrels);
}
@@ -653,8 +681,10 @@ AlterPublication(AlterPublicationStmt *stmt)
AlterPublicationOptions(stmt, rel, tup);
else if (stmt->schemas)
AlterPublicationSchemas(stmt, rel, tup, pubform);
+ else if (stmt->skiptables)
+ AlterPublicationTables(stmt, rel, tup, true);
else
- AlterPublicationTables(stmt, rel, tup);
+ AlterPublicationTables(stmt, rel, tup, false);
/* Cleanup. */
heap_freetuple(tup);
@@ -665,24 +695,36 @@ AlterPublication(AlterPublicationStmt *stmt)
* Remove relation from publication by mapping OID.
*/
void
-RemovePublicationRelById(Oid proid)
+RemovePublicationRelById(Oid proid, bool is_skip)
{
Relation rel;
HeapTuple tup;
- Form_pg_publication_rel pubrel;
+ Oid relid;
- rel = table_open(PublicationRelRelationId, RowExclusiveLock);
+ rel = table_open(GET_PUBLICATION_REL_ID(is_skip), RowExclusiveLock);
- tup = SearchSysCache1(PUBLICATIONREL, ObjectIdGetDatum(proid));
+ tup = SearchSysCache1(GET_PUBLICATION_SYSCACHE_REL(is_skip),
+ ObjectIdGetDatum(proid));
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication table %u",
proid);
- pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+ if (is_skip)
+ {
+ Form_pg_publication_skip pubrel;
+ pubrel = (Form_pg_publication_skip) GETSTRUCT(tup);
+ relid = pubrel->psrelid;
+ }
+ else
+ {
+ Form_pg_publication_rel pubrel;
+ pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+ relid = pubrel->prrelid;
+ }
/* Invalidate relcache so that publication info is rebuilt. */
- CacheInvalidateRelcacheByRelid(pubrel->prrelid);
+ CacheInvalidateRelcacheByRelid(relid);
CatalogTupleDelete(rel, &tup->t_self);
@@ -821,11 +863,14 @@ CloseTableList(List *rels)
*/
static void
PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+ AlterPublicationStmt *stmt, bool is_skip)
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
+ if (is_skip)
+ Assert(!stmt || !stmt->tables);
+ else
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -837,13 +882,13 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists, is_skip);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
(Node *) stmt);
- InvokeObjectPostCreateHook(PublicationRelRelationId,
+ InvokeObjectPostCreateHook(is_skip ? PublicationSkipRelationId : PublicationRelRelationId,
obj.objectId, 0);
}
}
@@ -886,7 +931,7 @@ PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
* Remove listed tables from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropTables(Oid pubid, List *rels, bool missing_ok, bool is_skip)
{
ObjectAddress obj;
ListCell *lc;
@@ -897,7 +942,8 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
- prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
+ prid = GetSysCacheOid2(GET_PUBLICATION_MAP(is_skip),
+ GET_PUBLICATION_COL_OBJECTID(is_skip),
ObjectIdGetDatum(relid),
ObjectIdGetDatum(pubid));
if (!OidIsValid(prid))
@@ -907,11 +953,12 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("relation \"%s\" is not part of the publication",
+ errmsg("%s \"%s\" is not part of the publication",
+ GET_PUBLICATION_REL_STR(is_skip),
RelationGetRelationName(rel))));
}
- ObjectAddressSet(obj, PublicationRelRelationId, prid);
+ ObjectAddressSet(obj, GET_PUBLICATION_REL_ID(is_skip), prid);
performDeletion(&obj, DROP_CASCADE, 0);
}
}
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index b108b641c5..f32b4e52c9 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -81,6 +81,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
case OBJECT_PUBLICATION_SCHEMA:
+ case OBJECT_PUBLICATION_SKIP:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d57cc0bcba..61d18da4b2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12141,6 +12141,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
case OCLASS_PUBLICATION_SCHEMA:
+ case OCLASS_PUBLICATION_SKIP:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 81e508b7a5..b852de6da8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -396,6 +396,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <importqual> import_qualification
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
+%type <list> opt_skip_table_list
%type <list> parse_toplevel stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
@@ -707,7 +708,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
- SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
+ SIMILAR SIMPLE SKIP SKIP_TABLE SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
@@ -9587,11 +9588,11 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
*
* CREATE PUBLICATION name [WITH options]
*
- * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ * CREATE PUBLICATION FOR ALL TABLES SKIP_TABLE [WITH options]
*
* CREATE PUBLICATION FOR TABLE [WITH options]
*
- * CREATE PUBLICATION FOR SCHEMA [WITH options]
+ * CREATE PUBLICATION FOR SCHEMA SKIP_TABLE [WITH options]
*****************************************************************************/
CreatePublicationStmt:
@@ -9602,11 +9603,12 @@ CreatePublicationStmt:
n->options = $4;
$$ = (Node *)n;
}
- | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ | CREATE PUBLICATION name FOR ALL TABLES opt_skip_table_list opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $7;
+ n->options = $8;
+ n->skiptables = (List *)$7;
n->for_all_tables = true;
$$ = (Node *)n;
}
@@ -9618,12 +9620,13 @@ CreatePublicationStmt:
n->tables = (List *)$6;
$$ = (Node *)n;
}
- | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_skip_table_list opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $7;
+ n->options = $8;
n->schemas = (List *)$6;
+ n->skiptables = (List *)$7;
$$ = (Node *)n;
}
;
@@ -9648,6 +9651,10 @@ schema_list: SchemaSpec
{ $$ = lappend($1, $3); }
;
+opt_skip_table_list: SKIP_TABLE relation_expr_list { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
@@ -9658,6 +9665,12 @@ schema_list: SchemaSpec
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SKIP_TABLE table [, table2]
+ *
+ * ALTER PUBLICATION name DROP SKIP_TABLE table [, table2]
+ *
+ * ALTER PUBLICATION name SET SKIP_TABLE table [, table2]
+ *
* ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
*
* ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
@@ -9697,6 +9710,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SKIP_TABLE relation_expr_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->skiptables = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SKIP_TABLE relation_expr_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->skiptables = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SKIP_TABLE relation_expr_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->skiptables = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
| ALTER PUBLICATION name ADD_P SCHEMA schema_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
@@ -15767,6 +15804,7 @@ unreserved_keyword:
| SHOW
| SIMPLE
| SKIP
+ | SKIP_TABLE
| SNAPSHOT
| SQL_P
| STABLE
@@ -16355,6 +16393,7 @@ bare_label_keyword:
| SIMILAR
| SIMPLE
| SKIP
+ | SKIP_TABLE
| SMALLINT
| SNAPSHOT
| SOME
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 1057acd62e..6eb84a00d2 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1064,23 +1064,30 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->pubtype == PUBTYPE_ALLTABLES)
+ if (pub->pubtype == PUBTYPE_ALLTABLES ||
+ pub->pubtype == PUBTYPE_SCHEMA)
{
- publish = true;
- if (pub->pubviaroot && am_partition)
- publish_as_relid = llast_oid(get_partition_ancestors(relid));
- }
-
- if (pub->pubtype == PUBTYPE_SCHEMA)
- {
- Oid schemaId = get_rel_namespace(relid);
- List *pubschemas = GetPublicationSchemas(pub->oid);
-
- if (list_member_oid(pubschemas, schemaId))
+ List *skipRels = GetPublicationRelations(pub->oid,
+ pub->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF,
+ true);
+
+ /* Check if publishing this relation should be skipped */
+ if (!list_member_oid(skipRels, relid))
{
- publish = true;
- if (pub->pubviaroot && am_partition)
- publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ Oid schemaId = (pub->pubtype == PUBTYPE_SCHEMA) ?
+ get_rel_namespace(relid) : InvalidOid;
+ List *pubschemas = (pub->pubtype == PUBTYPE_SCHEMA) ?
+ GetPublicationSchemas(pub->oid) : NIL;
+
+ if (pub->pubtype == PUBTYPE_ALLTABLES ||
+ list_member_oid(pubschemas, schemaId))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
}
}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index b2f8b8add8..10e6b1d783 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -52,6 +52,7 @@
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_publication_schema.h"
+#include "catalog/pg_publication_skiprel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -673,6 +674,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSkipRelationId, /* PUBLICATIONSKIP */
+ PublicationSkipObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_skiprel_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSkipRelationId, /* PUBLICATIONSKIPMAP */
+ PublicationSkipPsrelidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_skiprel_psrelid,
+ Anum_pg_publication_skiprel_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 773f038b24..ef70b86f0b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -255,7 +255,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
sizeof(PublicationInfo));
pg_log_info("reading publication membership");
- getPublicationTables(fout, tblinfo, numTables);
+ getPublicationTables(fout, tblinfo, numTables, false);
+
+ pg_log_info("reading publication skiptables");
+ getPublicationTables(fout, tblinfo, numTables, true);
pg_log_info("reading publciation schemas");
getPublicationSchemas(fout, nspinfo, numNamespaces);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dd049da209..110f893283 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -269,7 +269,9 @@ static void dumpBlob(Archive *fout, const BlobInfo *binfo);
static int dumpBlobs(Archive *fout, const void *arg);
static void dumpPolicy(Archive *fout, const PolicyInfo *polinfo);
static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo);
-static void dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo);
+static void dumpPublicationTable(Archive *fout,
+ const PublicationRelInfo *pubrinfo,
+ bool isskip);
static void dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo);
static void dumpDatabase(Archive *AH);
static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf,
@@ -4257,7 +4259,8 @@ getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
* get information about publication membership for dumpable tables.
*/
void
-getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
+getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables,
+ bool isSkip)
{
PQExpBuffer query;
PGresult *res;
@@ -4274,20 +4277,26 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
if (dopt->no_publications || fout->remoteVersion < 100000)
return;
+ if (isSkip && fout->remoteVersion < 140000)
+ return;
+
query = createPQExpBuffer();
/* Collect all publication membership info. */
- appendPQExpBufferStr(query,
- "SELECT tableoid, oid, prpubid, prrelid "
- "FROM pg_catalog.pg_publication_rel");
+ if (isSkip)
+ appendPQExpBufferStr(query, "SELECT tableoid, oid, pspubid, psrelid "
+ "FROM pg_catalog.pg_publication_skiprel");
+ else
+ appendPQExpBufferStr(query, "SELECT tableoid, oid, prpubid, prrelid "
+ "FROM pg_catalog.pg_publication_rel");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
ntups = PQntuples(res);
i_tableoid = PQfnumber(res, "tableoid");
i_oid = PQfnumber(res, "oid");
- i_prpubid = PQfnumber(res, "prpubid");
- i_prrelid = PQfnumber(res, "prrelid");
+ i_prpubid = PQfnumber(res, isSkip ? "pspubid" : "prpubid");
+ i_prrelid = PQfnumber(res, isSkip ? "psrelid" : "prrelid");
/* this allocation may be more than we need */
pubrinfo = pg_malloc(ntups * sizeof(PublicationRelInfo));
@@ -4319,7 +4328,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
continue;
/* OK, make a DumpableObject for this relationship */
- pubrinfo[j].dobj.objType = DO_PUBLICATION_REL;
+ pubrinfo[j].dobj.objType = (isSkip) ? DO_PUBLICATION_SKIPREL : DO_PUBLICATION_REL;
pubrinfo[j].dobj.catId.tableoid =
atooid(PQgetvalue(res, i, i_tableoid));
pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
@@ -4382,12 +4391,14 @@ dumpPublicationSchema(Archive *fout, PublicationSchemaInfo *pubrinfo)
* dump the definition of the given publication table mapping
*/
static void
-dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo)
+dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo,
+ bool isskip)
{
PublicationInfo *pubinfo = pubrinfo->publication;
TableInfo *tbinfo = pubrinfo->pubtable;
PQExpBuffer query;
char *tag;
+ char *addtabopt = (isskip) ? "SKIP_TABLE" : "TABLE ONLY";
if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
return;
@@ -4396,8 +4407,8 @@ dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo)
query = createPQExpBuffer();
- appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD TABLE ONLY",
- fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD %s",
+ fmtId(pubinfo->dobj.name), addtabopt);
appendPQExpBuffer(query, " %s;\n",
fmtQualifiedDumpable(tbinfo));
@@ -4412,7 +4423,7 @@ dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo)
ARCHIVE_OPTS(.tag = tag,
.namespace = tbinfo->dobj.namespace->dobj.name,
.owner = pubinfo->rolname,
- .description = "PUBLICATION TABLE",
+ .description = (isskip) ? "PUBLICATION SKIP TABLE" : "PUBLICATION TABLE",
.section = SECTION_POST_DATA,
.createStmt = query->data));
@@ -10479,11 +10490,14 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
dumpPublication(fout, (const PublicationInfo *) dobj);
break;
case DO_PUBLICATION_REL:
- dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
+ dumpPublicationTable(fout, (const PublicationRelInfo *) dobj, false);
break;
case DO_PUBLICATION_SCHEMA:
dumpPublicationSchema(fout, (PublicationSchemaInfo *) dobj);
break;
+ case DO_PUBLICATION_SKIPREL:
+ dumpPublicationTable(fout, (const PublicationRelInfo *) dobj, true);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18681,6 +18695,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
case DO_PUBLICATION_SCHEMA:
+ case DO_PUBLICATION_SKIPREL:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index a9db477f25..dd0873b93b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -82,6 +82,7 @@ typedef enum
DO_PUBLICATION,
DO_PUBLICATION_REL,
DO_PUBLICATION_SCHEMA,
+ DO_PUBLICATION_SKIPREL,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -744,7 +745,7 @@ extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
- int numTables);
+ int numTables, bool isSkip);
extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
int numSchemas);
extern void getSubscriptions(Archive *fout);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 13a6fcd660..298d3dcbc2 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -83,6 +83,7 @@ enum dbObjectTypePriorities
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
PRIO_PUBLICATION_SCHEMA,
+ PRIO_PUBLICATION_SKIPREL,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -137,6 +138,7 @@ static const int dbObjectTypePriority[] =
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
+ PRIO_PUBLICATION_SKIPREL, /* DO_PUBLICATION_SKIPREL */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1484,6 +1486,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION SCHEMA (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SKIPREL:
+ snprintf(buf, bufsize,
+ "PUBLICATION SKPREL (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 73417cbfc4..2184d2e27f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3154,7 +3154,8 @@ describeOneTableDetails(const char *schemaname,
"SELECT p.pubname\n"
"FROM pg_catalog.pg_publication p\n"
" JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
- " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s' AND\n"
+ " '%s' NOT IN (SELECT psrelid FROM pg_publication_skiprel WHERE pspubid = ps.pspubid)\n"
"UNION ALL\n"
"SELECT pubname\n"
"FROM pg_catalog.pg_publication p\n"
@@ -3163,10 +3164,11 @@ describeOneTableDetails(const char *schemaname,
"UNION ALL\n"
"SELECT pubname\n"
"FROM pg_catalog.pg_publication p\n"
- "WHERE p.pubtype = 'a' \n"
+ "WHERE p.pubtype = 'a' AND\n"
+ " '%s' NOT IN (SELECT psrelid FROM pg_publication_skiprel WHERE pspubid = p.oid)\n"
" AND pg_catalog.pg_relation_is_publishable('%s')\n"
"ORDER BY 1;",
- oid, oid, oid);
+ oid, oid, oid, oid, oid);
}
else
{
@@ -6345,6 +6347,7 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
bool has_pubtype;
+ bool has_pubskiptable;
PQExpBufferData title;
printTableContent cont;
@@ -6361,6 +6364,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
has_pubtype = (pset.sversion >= 140000);
+ has_pubskiptable = (pset.sversion >= 140000);
initPQExpBuffer(&buf);
@@ -6489,6 +6493,21 @@ describePublications(const char *pattern)
goto error_return;
}
+ if (has_pubskiptable)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname, c.relname\n"
+ "FROM pg_catalog.pg_class c,\n"
+ " pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_skiprel ps\n"
+ "WHERE c.relnamespace = n.oid\n"
+ " AND c.oid = ps.psrelid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Skip Tables:", false, &cont))
+ goto error_return;
+ }
+
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ba549e106e..63a672dd1f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1643,13 +1643,13 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
/* ALTER PUBLICATION <name> ADD */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
- COMPLETE_WITH("SCHEMA", "TABLE");
+ COMPLETE_WITH("SCHEMA", "SKIP_TABLE", "TABLE");
/* ALTER PUBLICATION <name> DROP */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
- COMPLETE_WITH("SCHEMA", "TABLE");
+ COMPLETE_WITH("SCHEMA", "SKIP_TABLE", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "SKIP_TABLE", "TABLE");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
COMPLETE_WITH_QUERY(Query_for_list_of_schemas
" UNION SELECT 'CURRENT_SCHEMA'");
@@ -2642,6 +2642,12 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
+ else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("SKIP_TABLE");
+ else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("FOR", "SCHEMA"))
+ COMPLETE_WITH("SKIP_TABLE");
+ else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("SKIP_TABLE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 08ec4c79f1..c82fa2765d 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -132,6 +132,7 @@ typedef enum ObjectClass
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
+ OCLASS_PUBLICATION_SKIP, /* pg_publication_skip */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f67f92f918..fff16bf76f 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -110,6 +110,33 @@ typedef enum PublicationPartOpt
#define PUBTYPE_SCHEMA 's' /* schema publication */
#define PUBTYPE_EMPTY 'e' /* empty publication */
+#define GET_PUBLICATION_REL_COL_COUNT(is_skip) \
+ (is_skip) ? Natts_pg_publication_skiprel : Natts_pg_publication_rel
+
+#define GET_PUBLICATION_REL_ID(is_skip) \
+ (is_skip) ? PublicationSkipRelationId : PublicationRelRelationId
+
+#define GET_PUBLICATION_MAP(is_skip) \
+ (is_skip) ? PUBLICATIONSKIPMAP : PUBLICATIONRELMAP
+
+#define GET_PUBLICATION_SYSCACHE_REL(is_skip) \
+ (is_skip) ? PUBLICATIONSKIP : PUBLICATIONREL
+
+#define GET_PUBLICATION_COL_OBJECTID(is_skip) \
+ (is_skip) ? Anum_pg_publication_skiprel_oid : Anum_pg_publication_rel_oid
+
+#define GET_PUBLICATION_COL_PUBID(is_skip) \
+ (is_skip) ? Anum_pg_publication_skiprel_pspubid : Anum_pg_publication_rel_prpubid
+
+#define GET_PUBLICATION_COL_RELID(is_skip) \
+ (is_skip) ? Anum_pg_publication_skiprel_psrelid : Anum_pg_publication_rel_prrelid
+
+#define GET_PUBLICATION_MULCOL_INDEXID(is_skip) \
+ (is_skip) ? PublicationSkipPsrelidPspubidIndexId : PublicationRelPrrelidPrpubidIndexId
+
+#define GET_PUBLICATION_REL_STR(is_skip) \
+ (is_skip) ? "skip relation": "relation"
+
/*
* Return the publication type.
*/
diff --git a/src/include/catalog/pg_publication_skiprel.h b/src/include/catalog/pg_publication_skiprel.h
new file mode 100644
index 0000000000..6bfc226c35
--- /dev/null
+++ b/src/include/catalog/pg_publication_skiprel.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_skiprel.h
+ * definition of the system catalog for mappings between relations and
+ * publications (pg_publication_skiprel)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_skiprel.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SKIPREL_H
+#define PG_PUBLICATION_SKIPREL_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_skiprel_d.h"
+
+/* ----------------
+ * pg_publication_skiprel definition. cpp turns this into
+ * typedef struct FormData_pg_publication_skiprel
+ * ----------------
+ */
+CATALOG(pg_publication_skiprel,6122,PublicationSkipRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psrelid BKI_LOOKUP(pg_class); /* Oid of the relation */
+} FormData_pg_publication_skip;
+
+/* ----------------
+ * Form_pg_publication_skip corresponds to a pointer to a tuple with
+ * the format of pg_publication_skip relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_skip *Form_pg_publication_skip;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_skip_oid_index, 6123, on pg_publication_skiprel using btree(oid oid_ops));
+#define PublicationSkipObjectIndexId 6123
+DECLARE_UNIQUE_INDEX(pg_publication_skip_psrelid_prpubid_index, 6124, on pg_publication_skiprel using btree(psrelid oid_ops, pspubid oid_ops));
+#define PublicationSkipPsrelidPspubidIndexId 6124
+
+#endif /* PG_PUBLICATION_SKIP_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 3c2a77d0b0..f3b803cf81 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -21,7 +21,7 @@
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
-extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationRelById(Oid proid, bool is_skip);
extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
@@ -31,15 +31,17 @@ extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ bool is_skip);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid,
+ List *pubskiptablelist);
extern List *GetAllSchemasPublicationRelations(Publication *publication);
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
+ bool if_not_exists, bool is_skip);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 169bdce07c..d1a89bc807 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1823,6 +1823,7 @@ typedef enum ObjectType
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
OBJECT_PUBLICATION_SCHEMA,
+ OBJECT_PUBLICATION_SKIP,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3648,6 +3649,7 @@ typedef struct CreatePublicationStmt
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
+ List *skiptables; /* Optional list of skip tables to add */
bool for_all_tables; /* Special publication for all tables in db */
List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
@@ -3662,6 +3664,7 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
List *tables; /* List of tables to add/drop */
+ List *skiptables; /* List of skip tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
List *schemas; /* Optional list of schemas */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..95b43396ea 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -385,6 +385,7 @@ PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("simple", SIMPLE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("skip", SKIP, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("skip_table", SKIP_TABLE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("some", SOME, RESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 1ba295206a..48a284edca 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -81,6 +81,8 @@ enum SysCacheIdentifier
PUBLICATIONRELMAP,
PUBLICATIONSCHEMA,
PUBLICATIONSCHEMAMAP,
+ PUBLICATIONSKIP,
+ PUBLICATIONSKIPMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index ddb421c394..d34b342248 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -262,6 +262,8 @@ NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
+NOTICE: checking pg_publication_skiprel {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_skiprel {psrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index fe5a038824..675d6503ab 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -141,6 +141,7 @@ pg_proc|t
pg_publication|t
pg_publication_rel|t
pg_publication_schema|t
+pg_publication_skiprel|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
--
2.25.1
v6-0004-Tests-and-documentation-for-skip_table-support-fo.patchtext/x-patch; charset=US-ASCII; name=v6-0004-Tests-and-documentation-for-skip_table-support-fo.patchDownload
From 962484dfea77017ec4ede92b5b9490d405b13390 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Wed, 16 Jun 2021 18:33:08 +0530
Subject: [PATCH v6 4/4] Tests and documentation for skip_table support for
publication.
Tests and documentation for skip_table support for publication.
---
doc/src/sgml/catalogs.sgml | 66 ++++++
doc/src/sgml/ref/alter_publication.sgml | 34 ++-
doc/src/sgml/ref/create_publication.sgml | 28 ++-
src/test/regress/expected/object_address.out | 10 +-
src/test/regress/expected/publication.out | 216 +++++++++++++++++++
src/test/regress/sql/object_address.sql | 5 +
src/test/regress/sql/publication.sql | 90 ++++++++
src/test/subscription/t/001_rep_changes.pl | 47 +++-
8 files changed, 491 insertions(+), 5 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b87d84a2e1..35ab340e34 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -250,6 +250,11 @@
<entry>schema to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-skiprel"><structname>pg_publication_skiprel</structname></link></entry>
+ <entry>skip relation to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6326,6 +6331,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-skiprel">
+ <title><structname>pg_publication_skiprel</structname></title>
+
+ <indexterm zone="catalog-pg-publication-skiprel">
+ <primary>pg_publication_skiprel</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_skiprel</structname> contains the
+ mapping between skip relations and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_skiprel</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psrelid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to relation
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 532ca2ff62..88eb8bd914 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -27,6 +27,9 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SKIP_TABLE <replaceable class="parameter">table_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SKIP_TABLE <replaceable class="parameter">table_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SKIP_TABLE <replaceable class="parameter">table_name</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -64,7 +67,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The seventh variant of this command listed in the synopsis can change
+ The seventh, eighth and ninth variants change which tables should be skipped
+ from the publication. The <literal>SET TABLE</literal> clause will replace
+ the list of skip tables in the publication with the specified one. The
+ <literal>ADD TABLE</literal> and <literal>DROP TABLE</literal> clauses will
+ add and remove one or more skip tables from the publication. Note that
+ adding skip tables to a publication that is already subscribed to will
+ require a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal>
+ action on the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The tenth variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -184,6 +198,24 @@ ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
Set schema to the publication:
<programlisting>
ALTER PUBLICATION production_publication SET SCHEMA production_july;
+</programlisting></para>
+
+ <para>
+ Add some skip tables to the publication:
+<programlisting>
+ALTER PUBLICATION mypublication ADD SKIP_TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Drop some skip tables from the publication:
+<programlisting>
+ALTER PUBLICATION mypublication DROP SKIP_TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Set some skip tables to the publication:
+<programlisting>
+ALTER PUBLICATION mypublication SET SKIP_TABLE users, departments;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 60fea8debe..5896a85b1f 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -23,8 +23,8 @@ PostgreSQL documentation
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
[ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
- | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
- | FOR ALL TABLES
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ] [ SKIP_TABLE <replaceable class="parameter">table_name</replaceable> [, ... ] ] ]
+ | FOR ALL TABLES [ SKIP_TABLE <replaceable class="parameter">table_name</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -111,6 +111,15 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>SKIP_TABLE</literal></term>
+ <listitem>
+ <para>
+ Specifies a list of tables to be skipped from the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -252,6 +261,21 @@ marketing and sales schemas:
<programlisting>
CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+marketing except marketing_temp table:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing SKIP_TABLE marketing_temp;
+</programlisting></para>
+
+ <para>
+ Create a publication that publishes all changes in all tables, except tbl1
+and tbl2 tables:
+<programlisting>
+CREATE PUBLICATION alltables FOR ALL TABLES SKIP_TABLE tbl1, tbl2;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 49ea22f427..9495c86ecf 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -46,6 +46,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
+CREATE PUBLICATION addr_pub_schema_skip FOR SCHEMA addr_nsp SKIP_TABLE addr_nsp.gentable;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -430,6 +431,7 @@ WITH objects (type, name, args) AS (VALUES
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
+ ('publication skiprelation', '{addr_nsp, gentable}', '{addr_pub_schema_skip}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -492,8 +494,9 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
+ publication skiprelation | | | addr_nsp.gentable in publication addr_pub_schema_skip | t
publication schema | | | addr_nsp in publication addr_pub_schema | t
-(50 rows)
+(51 rows)
---
--- Cleanup resources
@@ -506,6 +509,7 @@ drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
DROP PUBLICATION addr_pub_schema;
+DROP PUBLICATION addr_pub_schema_skip;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
@@ -573,6 +577,8 @@ WITH objects (classid, objid, objsubid) AS (VALUES
('pg_policy'::regclass, 0, 0), -- no policy
('pg_publication'::regclass, 0, 0), -- no publication
('pg_publication_rel'::regclass, 0, 0), -- no publication relation
+ ('pg_publication_schema'::regclass, 0, 0), -- no publication schema
+ ('pg_publication_skiprel'::regclass, 0, 0), -- no publication skip rel
('pg_subscription'::regclass, 0, 0), -- no subscription
('pg_transform'::regclass, 0, 0) -- no transformation
)
@@ -623,5 +629,7 @@ ORDER BY objects.classid, objects.objid, objects.objsubid;
("(subscription,,,)")|("(subscription,,)")|NULL
("(publication,,,)")|("(publication,,)")|NULL
("(""publication relation"",,,)")|("(""publication relation"",,)")|NULL
+("(""publication skiprelation"",,,)")|("(""publication skiprelation"",,)")|NULL
+("(""publication schema"",,,)")|("(""publication schema"",,)")|NULL
-- restore normal output mode
\a\t
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index bed28ef8a7..023ed3625b 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -554,6 +554,218 @@ ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
Schemas:
"public"
+-- CREATE publication SKIP_TABLE
+CREATE TABLE testpub_tbl1 (id serial primary key, data text);
+CREATE TABLE testpub_tbl2 (id serial primary key, data text);
+CREATE TABLE testpub_tbl3 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forskip FOR ALL TABLES SKIP_TABLE testpub_tbl1;
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+
+CREATE PUBLICATION testpub2_forskip FOR SCHEMA pub_test1 SKIP_TABLE pub_test1.tbl1;
+\dRp+ testpub2_forskip
+ Publication testpub2_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+Skip Tables:
+ "pub_test1.tbl1"
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forskip FOR SCHEMA CURRENT_SCHEMA SKIP_TABLE testpub_tbl2;
+\dRp+ testpub3_forskip
+ Publication testpub3_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+Skip Tables:
+ "public.testpub_tbl2"
+
+CREATE PUBLICATION testpub4_forskip FOR SCHEMA CURRENT_SCHEMA SKIP_TABLE testpub_tbl3;
+RESET client_min_messages;
+\dRp+ testpub4_forskip
+ Publication testpub4_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+Skip Tables:
+ "public.testpub_tbl3"
+
+--- Check create publication on a table that does not exist
+CREATE PUBLICATION testpub_forskip FOR ALL TABLES SKIP_TABLE non_existent_table;
+ERROR: relation "non_existent_table" does not exist
+--- Check create publication on a object which is not a table
+CREATE PUBLICATION testpub_forkip FOR ALL TABLES SKIP_TABLE testpub_view;
+ERROR: "testpub_view" is not a table
+DETAIL: Only tables can be added to publications.
+-- Dropping the table should reflect the change in publication
+DROP TABLE testpub_tbl3;
+\dRp+ testpub4_forskip
+ Publication testpub4_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+-- Renaming the table should reflect the change in publication
+ALTER TABLE testpub_tbl2 RENAME to testpub_tbl2_renamed;
+\dRp+ testpub3_forskip
+ Publication testpub3_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+Skip Tables:
+ "public.testpub_tbl2_renamed"
+
+ALTER TABLE testpub_tbl2_renamed RENAME to testpub_tbl2;
+\dRp+ testpub3_forskip
+ Publication testpub3_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+Skip Tables:
+ "public.testpub_tbl2"
+
+-- Alter publication add table
+ALTER PUBLICATION testpub1_forskip ADD SKIP_TABLE testpub_tbl2;
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+ "public.testpub_tbl2"
+
+-- Add non existent table
+ALTER PUBLICATION testpub1_forskip ADD SKIP_TABLE non_existent_table;
+ERROR: relation "non_existent_table" does not exist
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+ "public.testpub_tbl2"
+
+-- Add a table which is already added to the publication
+ALTER PUBLICATION testpub1_forskip ADD SKIP_TABLE testpub_tbl2;
+ERROR: skip relation "testpub_tbl2" is already member of publication "testpub1_forskip"
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+ "public.testpub_tbl2"
+
+-- Alter publication drop table
+ALTER PUBLICATION testpub1_forskip DROP SKIP_TABLE testpub_tbl2;
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+
+-- Drop table that is not preset in the publication
+ALTER PUBLICATION testpub1_forskip DROP SKIP_TABLE testpub_tbl2;
+ERROR: skip relation "testpub_tbl2" is not part of the publication
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+
+-- Drop a table that does not exist in the system
+ALTER PUBLICATION testpub1_forskip DROP SKIP_TABLE non_existent_table;
+ERROR: relation "non_existent_table" does not exist
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+
+-- Drop all tables
+ALTER PUBLICATION testpub1_forskip DROP SKIP_TABLE testpub_tbl1;
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+(1 row)
+
+-- Alter publication set SKIP_TABLE
+ALTER PUBLICATION testpub1_forskip SET SKIP_TABLE testpub_tbl1;
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+
+-- Alter publication set multiple SKIP_TABLE
+ALTER PUBLICATION testpub1_forskip SET SKIP_TABLE testpub_tbl1, testpub_tbl2;
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+ "public.testpub_tbl2"
+
+-- Alter publication set non-existent table
+ALTER PUBLICATION testpub1_forskip SET SKIP_TABLE non_existent_table;
+ERROR: relation "non_existent_table" does not exist
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+ "public.testpub_tbl2"
+
+-- Alter publication set it with the same skip tables
+ALTER PUBLICATION testpub1_forskip SET SKIP_TABLE testpub_tbl1, testpub_tbl2;
+\dRp+ testpub1_forskip
+ Publication testpub1_forskip
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | t | t | f | a
+Skip Tables:
+ "public.testpub_tbl1"
+ "public.testpub_tbl2"
+
+DROP TABLE testpub_tbl1;
+DROP TABLE testpub_tbl2;
DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
@@ -561,6 +773,10 @@ DROP PUBLICATION testpub_fortbl;
DROP PUBLICATION testpub1_forschema;
DROP PUBLICATION testpub2_forschema;
DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub1_forskip;
+DROP PUBLICATION testpub2_forskip;
+DROP PUBLICATION testpub3_forskip;
+DROP PUBLICATION testpub4_forskip;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
DROP SCHEMA pub_test1 CASCADE;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 56d9b852fd..db3d113b14 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -49,6 +49,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
+CREATE PUBLICATION addr_pub_schema_skip FOR SCHEMA addr_nsp SKIP_TABLE addr_nsp.gentable;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -200,6 +201,7 @@ WITH objects (type, name, args) AS (VALUES
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
+ ('publication skiprelation', '{addr_nsp, gentable}', '{addr_pub_schema_skip}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -218,6 +220,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
DROP PUBLICATION addr_pub_schema;
+DROP PUBLICATION addr_pub_schema_skip;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
@@ -274,6 +277,8 @@ WITH objects (classid, objid, objsubid) AS (VALUES
('pg_policy'::regclass, 0, 0), -- no policy
('pg_publication'::regclass, 0, 0), -- no publication
('pg_publication_rel'::regclass, 0, 0), -- no publication relation
+ ('pg_publication_schema'::regclass, 0, 0), -- no publication schema
+ ('pg_publication_skiprel'::regclass, 0, 0), -- no publication skip rel
('pg_subscription'::regclass, 0, 0), -- no subscription
('pg_transform'::regclass, 0, 0) -- no transformation
)
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d55a6f42b3..d96699e92f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -272,6 +272,91 @@ ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
\dRp+ testpub1_forschema
+-- CREATE publication SKIP_TABLE
+CREATE TABLE testpub_tbl1 (id serial primary key, data text);
+CREATE TABLE testpub_tbl2 (id serial primary key, data text);
+CREATE TABLE testpub_tbl3 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forskip FOR ALL TABLES SKIP_TABLE testpub_tbl1;
+\dRp+ testpub1_forskip
+
+CREATE PUBLICATION testpub2_forskip FOR SCHEMA pub_test1 SKIP_TABLE pub_test1.tbl1;
+\dRp+ testpub2_forskip
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forskip FOR SCHEMA CURRENT_SCHEMA SKIP_TABLE testpub_tbl2;
+\dRp+ testpub3_forskip
+
+CREATE PUBLICATION testpub4_forskip FOR SCHEMA CURRENT_SCHEMA SKIP_TABLE testpub_tbl3;
+RESET client_min_messages;
+\dRp+ testpub4_forskip
+
+--- Check create publication on a table that does not exist
+CREATE PUBLICATION testpub_forskip FOR ALL TABLES SKIP_TABLE non_existent_table;
+
+--- Check create publication on a object which is not a table
+CREATE PUBLICATION testpub_forkip FOR ALL TABLES SKIP_TABLE testpub_view;
+
+-- Dropping the table should reflect the change in publication
+DROP TABLE testpub_tbl3;
+\dRp+ testpub4_forskip
+
+-- Renaming the table should reflect the change in publication
+ALTER TABLE testpub_tbl2 RENAME to testpub_tbl2_renamed;
+\dRp+ testpub3_forskip
+
+ALTER TABLE testpub_tbl2_renamed RENAME to testpub_tbl2;
+\dRp+ testpub3_forskip
+
+-- Alter publication add table
+ALTER PUBLICATION testpub1_forskip ADD SKIP_TABLE testpub_tbl2;
+\dRp+ testpub1_forskip
+
+-- Add non existent table
+ALTER PUBLICATION testpub1_forskip ADD SKIP_TABLE non_existent_table;
+\dRp+ testpub1_forskip
+
+-- Add a table which is already added to the publication
+ALTER PUBLICATION testpub1_forskip ADD SKIP_TABLE testpub_tbl2;
+\dRp+ testpub1_forskip
+
+-- Alter publication drop table
+ALTER PUBLICATION testpub1_forskip DROP SKIP_TABLE testpub_tbl2;
+\dRp+ testpub1_forskip
+
+-- Drop table that is not preset in the publication
+ALTER PUBLICATION testpub1_forskip DROP SKIP_TABLE testpub_tbl2;
+\dRp+ testpub1_forskip
+
+-- Drop a table that does not exist in the system
+ALTER PUBLICATION testpub1_forskip DROP SKIP_TABLE non_existent_table;
+\dRp+ testpub1_forskip
+
+-- Drop all tables
+ALTER PUBLICATION testpub1_forskip DROP SKIP_TABLE testpub_tbl1;
+\dRp+ testpub1_forskip
+
+-- Alter publication set SKIP_TABLE
+ALTER PUBLICATION testpub1_forskip SET SKIP_TABLE testpub_tbl1;
+\dRp+ testpub1_forskip
+
+-- Alter publication set multiple SKIP_TABLE
+ALTER PUBLICATION testpub1_forskip SET SKIP_TABLE testpub_tbl1, testpub_tbl2;
+\dRp+ testpub1_forskip
+
+-- Alter publication set non-existent table
+ALTER PUBLICATION testpub1_forskip SET SKIP_TABLE non_existent_table;
+\dRp+ testpub1_forskip
+
+-- Alter publication set it with the same skip tables
+ALTER PUBLICATION testpub1_forskip SET SKIP_TABLE testpub_tbl1, testpub_tbl2;
+\dRp+ testpub1_forskip
+
+DROP TABLE testpub_tbl1;
+DROP TABLE testpub_tbl2;
+
DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
@@ -280,6 +365,11 @@ DROP PUBLICATION testpub_fortbl;
DROP PUBLICATION testpub1_forschema;
DROP PUBLICATION testpub2_forschema;
DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub1_forskip;
+DROP PUBLICATION testpub2_forskip;
+DROP PUBLICATION testpub3_forskip;
+DROP PUBLICATION testpub4_forskip;
+
DROP SCHEMA pub_test CASCADE;
DROP SCHEMA pub_test1 CASCADE;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 36aa4393b7..316f9ec242 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 44;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -392,6 +392,51 @@ $node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_skip FOR SCHEMA sch1 SKIP_TABLE sch1.tab2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_skip CONNECTION '$publisher_connstr' PUBLICATION tap_pub_skip"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_skip');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(0||), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_skip");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_skip");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Wed, Jun 16, 2021 at 4:59 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Sat, Jan 9, 2021 at 8:08 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:And, with this feature, since there can be many huge tables inside a
schema, the initial table sync phase of the replication can take a
while.Say a user has created a publication for a schema with hundreds of
tables in it, at some point later, can he stop replicating a single or
some tables from that schema?Isn't this applies to FOR ALL TABLES syntax as well where the user
might want to exclude one or a few tables? I am not sure if it is a
good idea to deal with this as part of this patch.
Yes, this applies to "FOR ALL TABLES" publications also, i had worked for
this, the patch for the same is attached at [1]/messages/by-id/CALDaNm10g2h29a-oFHsadk-Du6RDhnVQT_vfTGqR82DsjxQLqQ@mail.gmail.com. I had created a separate
patch for this so that it is easier for reviewing.
[1]: /messages/by-id/CALDaNm10g2h29a-oFHsadk-Du6RDhnVQT_vfTGqR82DsjxQLqQ@mail.gmail.com
/messages/by-id/CALDaNm10g2h29a-oFHsadk-Du6RDhnVQT_vfTGqR82DsjxQLqQ@mail.gmail.com
Regards,
Vignesh
On Wed, Jan 20, 2021 at 6:27 PM Rahila Syed <rahilasyed90@gmail.com> wrote:
Hi Vignesh,
I have handled the above scenario(drop schema should automatically
remove the schema entry from publication schema relation) & addition
of tests in the new v2 patch attached.
Thoughts?Please see some initial comments:
1. I think there should be more tests to show that the schema data is
actually replicated
to the subscriber. Currently, I am not seeing the data being replicated
when I use FOR SCHEMA.
2. How does replication behave when a table is added or removed from a
subscribed schema
using ALTER TABLE SET SCHEMA?
3. Can we have a default schema like a public or current schema that gets
replicated in case the user didn't
specify one, this can be handy to replicate current schema tables.
I felt supporting a syntax like below will be useful for supporting current
schema:
create publication testpub for schema schema_name
or
create publication testpub for schema CURRENT_SCHEMA
Let the user specify CURRENT_SCHEMA explicitly if required instead of not
specifying anything.
I have implemented similar lines in the V6 patch at [1]/messages/by-id/CALDaNm10g2h29a-oFHsadk-Du6RDhnVQT_vfTGqR82DsjxQLqQ@mail.gmail.com. Thoughts?
[1]: /messages/by-id/CALDaNm10g2h29a-oFHsadk-Du6RDhnVQT_vfTGqR82DsjxQLqQ@mail.gmail.com
/messages/by-id/CALDaNm10g2h29a-oFHsadk-Du6RDhnVQT_vfTGqR82DsjxQLqQ@mail.gmail.com
Regards,
Vignesh
On Thu, Jun 17, 2021 at 12:41 AM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting it, the attached patch is a rebased version of
the patch with few review comment fixes, I will reply with the comment
fixes to the respective mails.
I've applied the patch, it applies cleand and ran "make check" and
tests run fine.
Some comments for patch 1:
In the commit message, some grammar mistakes:
"Changes was done in
pg_dump to handle pubtype updation in pg_publication table while the database
gets upgraded."
-------------- change to --
Changes were done in
pg_dump to handle pubtype updation in pg_publication table while the database
gets upgraded.
=============
Prototypes present in pg_publication.h was moved to publicationcmds.h so
that minimal datastructures ...
----------------- change to --
Prototypes present in pg_publication.h were moved to publicationcmds.h so
that minimal datastructures ..
========================
In patch 1:
In getObjectDescription()
+ if (!nspname)
+ {
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
Why free nspname if it is NULL?
Same comment in getObjectIdentityParts()
============================
In GetAllTablesPublicationRelations()
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Here you have init key[1], but the code below in the same function
inits key[0]. I am not sure if this will affect that code below.
if (pubviaroot)
{
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
=================================
in UpdatePublicationTypeTupleValue():
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
Not sure if this tup needs to be freed or if the memory context will
take care of it.
=====================
regards,
Ajin Cherian
Fujitsu Australia
Thanks for the comments.
On Fri, Jun 18, 2021 at 5:25 PM Ajin Cherian <itsajin@gmail.com> wrote:
On Thu, Jun 17, 2021 at 12:41 AM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting it, the attached patch is a rebased version of
the patch with few review comment fixes, I will reply with the comment
fixes to the respective mails.I've applied the patch, it applies cleand and ran "make check" and
tests run fine.Some comments for patch 1:
In the commit message, some grammar mistakes:
"Changes was done in
pg_dump to handle pubtype updation in pg_publication table while the database
gets upgraded."-------------- change to --
Changes were done in
pg_dump to handle pubtype updation in pg_publication table while the database
gets upgraded.
I will modify this.
=============
Prototypes present in pg_publication.h was moved to publicationcmds.h so
that minimal datastructures ...----------------- change to --
Prototypes present in pg_publication.h were moved to publicationcmds.h so
that minimal datastructures ..========================
I will modify this.
In patch 1:
In getObjectDescription()
+ if (!nspname) + { + pfree(pubname); + pfree(nspname); + ReleaseSysCache(tup);Why free nspname if it is NULL?
Same comment in getObjectIdentityParts()
I will modify this.
============================
In GetAllTablesPublicationRelations()
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0], + ScanKeyInit(&key[keycount++],Here you have init key[1], but the code below in the same function
inits key[0]. I am not sure if this will affect that code below.if (pubviaroot)
{
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
I felt this is ok as we specify the keycount to be 1, so only the
key[0] will be used. Thoughts?
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
scan = table_beginscan_catalog(classRel, 1, key);
=================================
in UpdatePublicationTypeTupleValue():
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, + replaces); + + /* Update the catalog. */ + CatalogTupleUpdate(rel, &tup->t_self, tup);Not sure if this tup needs to be freed or if the memory context will
take care of it.
I felt this is ok, as the cleanup is handled in the caller function
"AlterPublication", thoughts?
/* Cleanup. */
heap_freetuple(tup);
table_close(rel, RowExclusiveLock);
I will post an update patch for the fixes shortly.
Regards,
Vignesh
On Mon, Jun 21, 2021 at 3:16 PM vignesh C <vignesh21@gmail.com> wrote:
I felt this is ok as we specify the keycount to be 1, so only the
key[0] will be used. Thoughts?
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));scan = table_beginscan_catalog(classRel, 1, key);
It maybe fine, just doesn't look correct when you look at the function
as a whole.
=================================
in UpdatePublicationTypeTupleValue():
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, + replaces); + + /* Update the catalog. */ + CatalogTupleUpdate(rel, &tup->t_self, tup);Not sure if this tup needs to be freed or if the memory context will
take care of it.I felt this is ok, as the cleanup is handled in the caller function
"AlterPublication", thoughts?
/* Cleanup. */
heap_freetuple(tup);
table_close(rel, RowExclusiveLock);
that should be fine.
regards,
Ajin Cherian
Fujitsu Australia
On Tue, Jun 22, 2021 at 6:57 AM Ajin Cherian <itsajin@gmail.com> wrote:
On Mon, Jun 21, 2021 at 3:16 PM vignesh C <vignesh21@gmail.com> wrote:
I felt this is ok as we specify the keycount to be 1, so only the
key[0] will be used. Thoughts?
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));scan = table_beginscan_catalog(classRel, 1, key);
It maybe fine, just doesn't look correct when you look at the function
as a whole.
I have added a local variable for this to avoid confusion.
Updated patch has the fix for this, this also includes the fixes for
the other comments you had given.
I have removed the skip table patches to keep the focus on the main
patch, once this patch gets into committable shape, I will focus on
the skip table patch.
Regards,
Vignesh
Attachments:
v7-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v7-0001-Added-schema-level-support-for-publication.patchDownload
From d3df4e9e798ffa370170b5596821e19567d0bbb5 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 14 Jun 2021 10:23:23 +0530
Subject: [PATCH v7 1/2] Added schema level support for publication.
This patch adds schema level support for publication. User can specify multiple
schemas with schema option. When user specifies schema option, then the tables
present in the schema specified will be selected by publisher for sending the
data to subscriber.
pg_publication maintains the information about the publication. puballtables
bool column was used to indicate if the publication is "FOR ALL TABLES" or
"FOR TABLE" type currently. With the introduction of "FOR SCHEMA" publication
type, it is not easy to determine the publication type, hence a new column
pubtype was added to pg_publication relation to indicate the publication type.
There is a possibility to do without addition of new column, but that will
require checking puballtables of pg_publication and checking pg_publication_rel
for table type publication and then checking pg_publication_schema for schema
type publication. I preferred to use introduce pubtype which makes things
easier, this also will help for supporting new options in the future. New
system table pg_publication_schema was added which will maintain the schemas
that user wanted to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schema to the publication/publication_schema
when the schema is renamed/dropped. Decoder identifies if the relation is part
of the publication and replicates it to the subscriber. Changes were done in
pg_dump to handle pubtype updation in pg_publication table while the database
gets upgraded.
Prototypes present in pg_publication.h were moved to publicationcmds.h so
that minimal datastructures can be exported to pg_dump and psql clients and the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing as this feature
involves catalog change.
TODO: version checks for psql/pg_dump need to be changed from 140000 to 150000
once the ongoing release is completed.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 145 ++++++++++
src/backend/catalog/pg_publication.c | 170 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/lockcmds.c | 1 +
src/backend/commands/publicationcmds.c | 296 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 16 +-
src/backend/utils/cache/relcache.c | 1 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 165 ++++++++++-
src/bin/pg_dump/pg_dump.h | 16 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 218 +++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 41 +--
src/include/catalog/pg_publication_schema.h | 49 ++++
src/include/commands/publicationcmds.h | 21 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 3 +
33 files changed, 1304 insertions(+), 166 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 69f9dd51a7..30026a967b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -67,8 +67,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 53392414f1..59600fc98d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3433,6 +3433,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3572,6 +3573,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0c37fc1d53..c0a9fb0c7e 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -179,6 +180,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1470,6 +1472,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2863,6 +2869,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..b4e31f8157 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,51 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+ return address;
+ }
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2265,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2358,9 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ objnode = (Node *) list_make2(linitial(name), linitial(args));
+ break;
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3964,44 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4576,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5815,47 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ if (objname)
+ *objname = list_make1(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 86e415af89..aeb2df8e5c 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,76 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +377,45 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +454,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +500,13 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +525,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets list of all relation published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Publication *publication)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(publication->oid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(publication->pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +575,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +675,16 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication);
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..34cf049632 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270ced..3732f3727d 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -17,6 +17,7 @@
#include "access/table.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c8e0..5fe4b1ad6f 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(List *options,
@@ -141,6 +146,52 @@ parse_publication_options(List *options,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemoid;
+
+ switch (schema->schematype)
+ {
+ List *search_path;
+ char *nspname;
+
+ case SCHEMASPEC_CURRENT_SCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ nspname = get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemoid = get_namespace_oid(nspname, false);
+ break;
+
+ default:
+ schemoid = get_namespace_oid(schema->schemaname, false);
+ break;
+ }
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -213,6 +264,15 @@ CreatePublication(CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -226,6 +286,20 @@ CreatePublication(CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -252,6 +326,32 @@ CreatePublication(CreatePublicationStmt *stmt)
return myself;
}
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -311,7 +411,7 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
@@ -363,19 +463,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -427,11 +539,90 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, schemaoidlist)
+ {
+ Oid newschemaid = lfirst_oid(newlc);
+
+ if (newschemaid == oldschemaid)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +651,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -498,6 +691,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -608,7 +825,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -632,6 +849,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -666,6 +916,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -697,7 +981,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 6906714298..b108b641c5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4e23c7fce5..cffc3c263c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12138,6 +12139,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb24195438..81e508b7a5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/* makeSchemaSpec
+ * Create a SchemaSpec with the given type
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 63f108f960..1057acd62e 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -16,6 +16,7 @@
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1063,13 +1064,26 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ List *pubschemas = GetPublicationSchemas(pub->oid);
+
+ if (list_member_oid(pubschemas, schemaId))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
+
if (!publish)
{
bool ancestor_published = false;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d55ae016d0..671737acb2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e4dc4ee34e..b2f8b8add8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 24cc096255..719d537497 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7c3b..dd049da209 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -50,6 +50,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_trigger_d.h"
#include "catalog/pg_type_d.h"
#include "common/connect.h"
@@ -3958,6 +3959,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3972,25 +3974,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 140000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4008,6 +4022,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4032,6 +4047,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4074,7 +4090,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4141,6 +4157,101 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 140000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the table. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not member of any publications. Clean up and return.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4228,6 +4339,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10332,6 +10481,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18528,6 +18680,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a09c..a9db477f25 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -613,6 +614,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -626,6 +628,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -731,6 +745,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2abf255798..73417cbfc4 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 14000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 140000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for storing
+ * NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup("Publications:");
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,39 @@ listPublications(const char *pattern)
return true;
}
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6344,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6360,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 140000);
initPQExpBuffer(&buf);
@@ -6250,6 +6374,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6415,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6441,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6453,16 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 14 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6473,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6499,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 38af5682f2..f14543792e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2630,15 +2639,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fd44081e74..08ec4c79f1 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -131,6 +131,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 1b31fee9e3..f67f92f918 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -83,12 +85,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -105,16 +104,26 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype,"a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if(strcmp(strpubtype,"t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype,"s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..b0c9361d91
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, on pg_publication_schema using btree(oid oid_ops));
+#define PublicationSchemaObjectIndexId 8902
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+#define PublicationSchemaPsnspcidPspubidIndexId 8903
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 00e2e626e6..3c2a77d0b0 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,34 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(Publication *publication);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..dfeade5bf8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..169bdce07c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..28bf8daa64 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -85,10 +85,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -100,19 +100,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -131,10 +131,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -147,10 +147,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -170,10 +170,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -211,10 +211,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -258,10 +258,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -271,20 +271,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..fe5a038824 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index abdb08319c..b4d1c81898 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2031,6 +2031,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2322,6 +2323,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v7-0002-Tests-and-documentation-for-schema-level-support-.patchtext/x-patch; charset=US-ASCII; name=v7-0002-Tests-and-documentation-for-schema-level-support-.patchDownload
From 21f7829512cead245ef2c873b4b0669419b0e19f Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 21 Jun 2021 08:57:58 +0530
Subject: [PATCH v7 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 45 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 277 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 112 +++++++-
src/test/subscription/t/001_rep_changes.pl | 141 +++++++++-
8 files changed, 707 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f517a7d4af..b87d84a2e1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6171,6 +6176,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> and
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-altersubscription">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respctively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6238,6 +6265,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11274,9 +11362,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal> FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..532ca2ff62 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants change which schemas are part of the
+ publication. The <literal>SET SCHEMA</literal> clause will replace the list
+ of schemas in the publication with the specified one. The <literal>ADD
+ SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses will add and
+ remove one or more schemas from the publication. Note that adding schemas
+ to a publication that is already subscribed to will require a <literal>ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the subscribing side
+ in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..60fea8debe 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,17 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future. If
+ schema is not specified, publication will be created for CURRENT_SCHEMA.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +165,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +183,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +235,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+production schema:
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+marketing and sales schemas:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 28bf8daa64..bed28ef8a7 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -255,7 +255,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -287,11 +286,287 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..d55a6f42b3 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,122 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not preset in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index ca6cd2c646..617680d61c 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 45;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -266,6 +266,145 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Tue, Jun 22, 2021 at 9:45 AM vignesh C <vignesh21@gmail.com> wrote:
I have removed the skip table patches to keep the focus on the main
patch, once this patch gets into committable shape, I will focus on
the skip table patch.
IMO it's a good idea to start a new thread for the "skip table"
feature so that we can discuss it separately. If required you can
specify in that thread that the idea of the "skip table" can be
applied to the "add schema level support" feature.
With Regards,
Bharath Rupireddy.
On Tue, Jun 22, 2021 at 2:15 PM vignesh C <vignesh21@gmail.com> wrote:
Updated patch has the fix for this, this also includes the fixes for
the other comments you had given.
I have removed the skip table patches to keep the focus on the main
patch, once this patch gets into committable shape, I will focus on
the skip table patch.
I have the following initial comments on the v7 patches:
v7-0001
(1) The patch comment is pretty rough and needs work.
I suggest something like the following:
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
TODO: version checks for psql/pg_dump need to be changed from 140000 to 150000
once the ongoing release is completed.
(2) src/backend/catalog/objectaddress.c
getObjectIdentityParts(), case OCLASS_PUBLICATION_SCHEMA
Shouldn't pubname/nspname be pfree()d, if objargs/objname are NULL?
(3) src/backend/catalog/pg_publication.c
(i)
GetAllTablesPublications
BEFORE:
+ * Gets list of relations published.
AFTER:
+ * Gets the list of relations published.
There are several other cases of newly-added "Gets list of ..." comments.
(ii)
GetAllTablesPublicationRelations
BEFORE:
+ * Gets list of all relation published by FOR SCHEMA publication(s).
AFTER:
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
(4) src/backend/commands/publicationcmds.c
Missing function header for function "UpdatePublicationTypeTupleValue".
(5) src/backend/parser/gram.y
Violation of function header comment format:
BEFORE:
+/* makeSchemaSpec
+ * Create a SchemaSpec with the given type
+ */
AFTER:
+/*
+ * makeSchemaSpec
+ * Create a SchemaSpec with the given type
+ */
(6) src/bin/pg_dump/pg_dump.c
The following code is within a loop that processes schemas.
I think that (in the comment) "Clean up and return" should instead say
"Clean up and process the next schema"
Also, should say "Schema is not a member".
+ /*
+ * Schema is not member of any publications. Clean up and return.
+ */
+ PQclear(res);
+ continue;
(7) src/bin/psql/describe.c
Missing function header for function "addFooterToPublicationDesc".
v7-0002
(1) doc/src/sgml/catalogs.sgml
Typo
BEFORE:
+ respctively. The publication type cannot be changed in other cases.
AFTER:
+ respectively. The publication type cannot be changed in other cases.
(2) doc/src/sgml/ref/create_publication.sgml
BEFORE:
+ Create a publication that publishes all changes for all the tables
present in
+production schema:
AFTER:
+ Create a publication that publishes all changes for all the tables
present in
+the schema "production":
BEFORE:
+ Create a publication that publishes all changes for all the tables
present in
+marketing and sales schemas:
AFTER:
+ Create a publication that publishes all changes for all the tables
present in
+the schemas "marketing" and "sales":
(3) src/test/regress/expected/publication.out
BEFORE:
+-- Drop schema that is not preset in the publication
AFTER:
+-- Drop schema that is not present in the publication
BEFORE:
+--- Check create publication on a object which is not schema
AFTER:
+--- Check create publication on an object which is not schema
(4) src/test/regress/sql/publication.sql
BEFORE:
+-- Drop schema that is not preset in the publication
AFTER:
+-- Drop schema that is not present in the publication
Regards,
Greg Nancarrow
Fujitsu Australia
Hi
I applied your V7* patch and complied it. The following warnings came out, please take a look.
pg_publication.c:688:22: warning: ‘tables’ may be used uninitialized in this function [-Wmaybe-uninitialized]
funcctx->user_fctx = (void *) tables;
~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
describe.c: In function ‘describePublications’:
describe.c:6479:35: warning: ‘pubtype’ may be used uninitialized in this function [-Wmaybe-uninitialized]
else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
For the warning in pg_publication.c, maybe we can replace the following 'else if' with 'else'.
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
For the warning in describe.c, initialization of 'pubtype' is needed.
Regards
Tang
On Thu, Jun 24, 2021 at 4:41 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
I applied your V7* patch and complied it. The following warnings came out, please take a look.
I encountered the following warnings when building with the v7 patches applied:
(looks like missing #include "catalog/objectaddress.h")
pg_prewarm.c:109:29: warning: implicit declaration of function
‘get_relkind_objtype’; did you mean ‘get_element_type’?
[-Wimplicit-function-declaration]
aclcheck_error(aclresult,
get_relkind_objtype(rel->rd_rel->relkind), get_rel_name(relOid));
^~~~~~~~~~~~~~~~~~~
get_element_type
heap_surgery.c:391:9: warning: implicit declaration of function
‘get_relkind_objtype’; did you mean ‘get_publication_type’?
[-Wimplicit-function-declaration]
get_relkind_objtype(rel->rd_rel->relkind),
^~~~~~~~~~~~~~~~~~~
get_publication_type
pgrowlocks.c:136:29: warning: implicit declaration of function
‘get_relkind_objtype’ [-Wimplicit-function-declaration]
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
Regards,
Greg Nancarrow
Fujitsu Australia
On Wed, Jun 23, 2021 at 2:10 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Jun 22, 2021 at 2:15 PM vignesh C <vignesh21@gmail.com> wrote:
Updated patch has the fix for this, this also includes the fixes for
the other comments you had given.
I have removed the skip table patches to keep the focus on the main
patch, once this patch gets into committable shape, I will focus on
the skip table patch.I have the following initial comments on the v7 patches:
v7-0001
(1) The patch comment is pretty rough and needs work.
I suggest something like the following:
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.TODO: version checks for psql/pg_dump need to be changed from 140000 to 150000
once the ongoing release is completed.
Modified.
(2) src/backend/catalog/objectaddress.c
getObjectIdentityParts(), case OCLASS_PUBLICATION_SCHEMAShouldn't pubname/nspname be pfree()d, if objargs/objname are NULL?
Modified
(3) src/backend/catalog/pg_publication.c
(i)
GetAllTablesPublicationsBEFORE: + * Gets list of relations published. AFTER: + * Gets the list of relations published.There are several other cases of newly-added "Gets list of ..." comments.
Modified
(ii)
GetAllTablesPublicationRelationsBEFORE: + * Gets list of all relation published by FOR SCHEMA publication(s). AFTER: + * Gets the list of all relations published by FOR SCHEMA publication(s).
Modified
(4) src/backend/commands/publicationcmds.c
Missing function header for function "UpdatePublicationTypeTupleValue".
Included function header.
(5) src/backend/parser/gram.y
Violation of function header comment format:
BEFORE: +/* makeSchemaSpec + * Create a SchemaSpec with the given type + */AFTER: +/* + * makeSchemaSpec + * Create a SchemaSpec with the given type + */
Modified it to:
/*
* makeSchemaSpec - Create a SchemaSpec with the given type
*/
(6) src/bin/pg_dump/pg_dump.c
The following code is within a loop that processes schemas.
I think that (in the comment) "Clean up and return" should instead say
"Clean up and process the next schema"
Also, should say "Schema is not a member".+ /* + * Schema is not member of any publications. Clean up and return. + */ + PQclear(res); + continue;
Modified.
(7) src/bin/psql/describe.c
Missing function header for function "addFooterToPublicationDesc".
Included it.
v7-0002
(1) doc/src/sgml/catalogs.sgml
TypoBEFORE: + respctively. The publication type cannot be changed in other cases. AFTER: + respectively. The publication type cannot be changed in other cases.
Modified
(2) doc/src/sgml/ref/create_publication.sgml
BEFORE: + Create a publication that publishes all changes for all the tables present in +production schema: AFTER: + Create a publication that publishes all changes for all the tables present in +the schema "production":BEFORE: + Create a publication that publishes all changes for all the tables present in +marketing and sales schemas: AFTER: + Create a publication that publishes all changes for all the tables present in +the schemas "marketing" and "sales":
Modified.
(3) src/test/regress/expected/publication.out
BEFORE: +-- Drop schema that is not preset in the publication AFTER: +-- Drop schema that is not present in the publicationBEFORE: +--- Check create publication on a object which is not schema AFTER: +--- Check create publication on an object which is not schema
Modified.
(4) src/test/regress/sql/publication.sql
BEFORE: +-- Drop schema that is not preset in the publication AFTER: +-- Drop schema that is not present in the publication
Modified.
Thanks for reviewing and providing the comments, Attached v8 patches
have the fixes for the comments.
Regards,
Vignesh
Attachments:
v8-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v8-0001-Added-schema-level-support-for-publication.patchDownload
From b938cdb8268b5165cedd4e67a4639c12a1a43afe Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 14 Jun 2021 10:23:23 +0530
Subject: [PATCH v8 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
TODO: version checks for psql/pg_dump need to be changed from 140000 to 150000
once the ongoing release is completed.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 150 ++++++++++
src/backend/catalog/pg_publication.c | 173 +++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 298 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 16 +-
src/backend/utils/cache/relcache.c | 1 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 166 ++++++++++-
src/bin/pg_dump/pg_dump.h | 16 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 ++++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 42 +--
src/include/catalog/pg_publication_schema.h | 49 ++++
src/include/commands/publicationcmds.h | 21 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 4 +
33 files changed, 1321 insertions(+), 166 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 69f9dd51a7..30026a967b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -67,8 +67,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 53392414f1..59600fc98d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3433,6 +3433,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3572,6 +3573,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0c37fc1d53..6326681371 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -179,6 +180,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1470,6 +1472,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2863,6 +2869,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..0066e0e9c6 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,51 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+ return address;
+ }
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2265,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2358,9 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ objnode = (Node *) list_make2(linitial(name), linitial(args));
+ break;
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3964,44 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4576,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5815,52 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 86e415af89..ee4ecfd111 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,76 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +377,45 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +454,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +500,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +526,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Publication *publication)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(publication->oid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(publication->pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +576,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +676,18 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..34cf049632 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c8e0..9f96b8b785 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(List *options,
@@ -141,6 +146,51 @@ parse_publication_options(List *options,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemoid;
+ List *search_path;
+ char *nspname;
+
+ switch (schema->schematype)
+ {
+ case SCHEMASPEC_CURRENT_SCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ nspname = get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemoid = get_namespace_oid(nspname, false);
+ break;
+
+ default:
+ schemoid = get_namespace_oid(schema->schemaname, false);
+ break;
+ }
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -213,6 +263,15 @@ CreatePublication(CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -226,6 +285,20 @@ CreatePublication(CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -252,6 +325,35 @@ CreatePublication(CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -311,7 +413,7 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
@@ -363,19 +465,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -427,11 +541,90 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, schemaoidlist)
+ {
+ Oid newschemaid = lfirst_oid(newlc);
+
+ if (newschemaid == oldschemaid)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +653,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -498,6 +693,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -608,7 +827,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -632,6 +851,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -666,6 +918,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -697,7 +983,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 6906714298..b108b641c5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4e23c7fce5..cffc3c263c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12138,6 +12139,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb24195438..32a062d0ce 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 63f108f960..1057acd62e 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -16,6 +16,7 @@
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1063,13 +1064,26 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ List *pubschemas = GetPublicationSchemas(pub->oid);
+
+ if (list_member_oid(pubschemas, schemaId))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
+
if (!publish)
{
bool ancestor_published = false;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d55ae016d0..671737acb2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e4dc4ee34e..b2f8b8add8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 24cc096255..719d537497 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7c3b..7970a6dbbe 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -50,6 +50,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_trigger_d.h"
#include "catalog/pg_type_d.h"
#include "common/connect.h"
@@ -3958,6 +3959,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3972,25 +3974,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 140000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4008,6 +4022,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4032,6 +4047,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4074,7 +4090,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4141,6 +4157,102 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 140000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the table. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and process
+ * the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4228,6 +4340,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10332,6 +10482,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18528,6 +18681,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a09c..a9db477f25 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -613,6 +614,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -626,6 +628,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -731,6 +745,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2abf255798..ae36e027b8 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 14000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 140000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup("Publications:");
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 140000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 14 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 38af5682f2..f14543792e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2630,15 +2639,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fd44081e74..08ec4c79f1 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -131,6 +131,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 1b31fee9e3..93d77d2ffe 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -83,12 +85,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -105,16 +104,27 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables
+ * publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..cf977e2de6
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, on pg_publication_schema using btree(oid oid_ops));
+#define PublicationSchemaObjectIndexId 8902
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+#define PublicationSchemaPsnspcidPspubidIndexId 8903
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 00e2e626e6..3c2a77d0b0 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,34 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(Publication *publication);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..dfeade5bf8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..169bdce07c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 77d176a934..2c1e9a3a31 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..28bf8daa64 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -85,10 +85,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -100,19 +100,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -131,10 +131,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -147,10 +147,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -170,10 +170,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -211,10 +211,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -258,10 +258,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -271,20 +271,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..fe5a038824 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index abdb08319c..7c158afc9d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -776,6 +776,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -2031,6 +2032,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2322,6 +2324,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v8-0002-Tests-and-documentation-for-schema-level-support-.patchtext/x-patch; charset=US-ASCII; name=v8-0002-Tests-and-documentation-for-schema-level-support-.patchDownload
From 655d5461422a0e467ea8d0af0ad6fff4bf44553b Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 21 Jun 2021 08:57:58 +0530
Subject: [PATCH v8 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 45 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 277 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 112 +++++++-
src/test/subscription/t/001_rep_changes.pl | 141 +++++++++-
8 files changed, 707 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f517a7d4af..66fe960f92 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6171,6 +6176,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> and
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-altersubscription">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6238,6 +6265,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11274,9 +11362,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal> FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..532ca2ff62 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants change which schemas are part of the
+ publication. The <literal>SET SCHEMA</literal> clause will replace the list
+ of schemas in the publication with the specified one. The <literal>ADD
+ SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses will add and
+ remove one or more schemas from the publication. Note that adding schemas
+ to a publication that is already subscribed to will require a <literal>ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the subscribing side
+ in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..2529739dda 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,17 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future. If
+ schema is not specified, publication will be created for CURRENT_SCHEMA.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +165,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +183,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +235,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 28bf8daa64..e46e7cdd65 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -255,7 +255,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -287,11 +286,287 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..70a46172a5 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,122 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index ca6cd2c646..617680d61c 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 45;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -266,6 +266,145 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Thu, Jun 24, 2021 at 2:12 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Thu, Jun 24, 2021 at 4:41 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:I applied your V7* patch and complied it. The following warnings came
out, please take a look.
I encountered the following warnings when building with the v7 patches
applied:
(looks like missing #include "catalog/objectaddress.h")
pg_prewarm.c:109:29: warning: implicit declaration of function
‘get_relkind_objtype’; did you mean ‘get_element_type’?
[-Wimplicit-function-declaration]
aclcheck_error(aclresult,
get_relkind_objtype(rel->rd_rel->relkind), get_rel_name(relOid));
^~~~~~~~~~~~~~~~~~~
get_element_typeheap_surgery.c:391:9: warning: implicit declaration of function
‘get_relkind_objtype’; did you mean ‘get_publication_type’?
[-Wimplicit-function-declaration]
get_relkind_objtype(rel->rd_rel->relkind),
^~~~~~~~~~~~~~~~~~~
get_publication_typepgrowlocks.c:136:29: warning: implicit declaration of function
‘get_relkind_objtype’ [-Wimplicit-function-declaration]
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
Thanks for reporting these warnings, I have fixed this in the v8 patch
attached at [1]/messages/by-id/CALDaNm044P_cds1OxZvFse5rE_qQfhbUg5MdtMgsa7t_bZGJdw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm044P_cds1OxZvFse5rE_qQfhbUg5MdtMgsa7t_bZGJdw@mail.gmail.com
/messages/by-id/CALDaNm044P_cds1OxZvFse5rE_qQfhbUg5MdtMgsa7t_bZGJdw@mail.gmail.com
Regards,
Vignesh
On Thu, Jun 24, 2021 at 12:10 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
Hi
I applied your V7* patch and complied it. The following warnings came out, please take a look.
pg_publication.c:688:22: warning: ‘tables’ may be used uninitialized in this function [-Wmaybe-uninitialized]
funcctx->user_fctx = (void *) tables;
~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
describe.c: In function ‘describePublications’:
describe.c:6479:35: warning: ‘pubtype’ may be used uninitialized in this function [-Wmaybe-uninitialized]
else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)For the warning in pg_publication.c, maybe we can replace the following 'else if' with 'else'.
+ else if (publication->pubtype == PUBTYPE_SCHEMA)For the warning in describe.c, initialization of 'pubtype' is needed.
Thanks for reporting these warnings, I have fixed this in the v8 patch
attached at [1]/messages/by-id/CALDaNm044P_cds1OxZvFse5rE_qQfhbUg5MdtMgsa7t_bZGJdw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm044P_cds1OxZvFse5rE_qQfhbUg5MdtMgsa7t_bZGJdw@mail.gmail.com
Regards,
Vignesh
On Friday, June 25, 2021 2:25 AM vignesh C <vignesh21@gmail.com>wrote:
Thanks for reporting these warnings, I have fixed this in the v8 patch
attached at [1].
[1] - https://www.postgresql.org/message-
id/CALDaNm044P_cds1OxZvFse5rE_qQfhbUg5MdtMgsa7t_bZGJdw%40mail.
gmail.com
Thanks for your patch. The warnings are fixed.
But I found an issue while using your V8 patch, which is similar to [1]/messages/by-id/CALj2ACV+0UFpcZs5czYgBpujM9p0Hg1qdOZai_43OU7bqHU_xw@mail.gmail.com. The case is as below:
Drop a schema from publication and refresh publication at subscriber, then insert into publisher table, the inserts still replicated to subscriber. The expect result is that the data is no longer replicated.
For example:
------publisher------
create schema s1;
create table s1.t1 (a int primary key);
create publication pub for schema s1;
------subscriber------
create schema s1;
create table s1.t1 (a int primary key);
create subscription sub connection 'dbname=postgres port=5432' publication pub;
------publisher------
insert into s1.t1 values (1);
------subscriber------
postgres=# select * from s1.t1;
a
---
1
(1 row)
------publisher------
alter publication pub drop schema s1;
insert into s1.t1 values (2);
------subscriber------
postgres=# select * from s1.t1;
a
---
1
2
(2 rows)
The similar issue [1]/messages/by-id/CALj2ACV+0UFpcZs5czYgBpujM9p0Hg1qdOZai_43OU7bqHU_xw@mail.gmail.com (related to "ALTER PUBLICATION .. DROP TABLE") was fixed by modifying in rel_sync_cache_publication_cb callback, which is related to PUBLICATIONRELMAP syscache. In my case, I think it used PUBLICATIONSCHEMAMAP syscache, and no callback was registered for it. Should we register a callback for it or fix it in other ways?
[1]: /messages/by-id/CALj2ACV+0UFpcZs5czYgBpujM9p0Hg1qdOZai_43OU7bqHU_xw@mail.gmail.com
Regards
Tang
On Tuesday, June 29, 2021 11:25 AM tanghy.fnst@fujitsu.com <tanghy.fnst@fujitsu.com> wrote:
Thanks for your patch. The warnings are fixed.
But I found an issue while using your V8 patch, which is similar to [1]. The case
is as below:
Drop a schema from publication and refresh publication at subscriber, then
insert into publisher table, the inserts still replicated to subscriber. The expect
result is that the data is no longer replicated.
I also saw a problem while using "ALTER PUBLICATION ... ADD SCHEMA ...". The case is as below:
Add a schema to publication, then refresh publication at subscriber. Insert into publisher table, the inserts couldn't replicate to subscriber.
Steps to reproduce the case:
------publisher------
CREATE PUBLICATION testpub FOR SCHEMA public;
------subscriber------
CREATE SUBSCRIPTION testsub CONNECTION 'dbname=postgres port=5432' PUBLICATION testpub;
------publisher------
CREATE SCHEMA s1;
CREATE TABLE s1.t1 (a int PRIMARY KEY);
insert into s1.t1 values (1);
ALTER PUBLICATION testpub ADD SCHEMA s1;
------subscriber------
CREATE SCHEMA s1;
CREATE TABLE s1.t1 (a int PRIMARY KEY);
ALTER SUBSCRIPTION testsub REFRESH PUBLICATION;
postgres=# SELECT * FROM s1.t1;
a
---
1
(1 row)
------publisher------
insert into s1.t1 values (2);
------subscriber------
postgres=# SELECT * FROM s1.t1;
a
---
1
(1 row)
when I executed "ALTER PUBLICATION ... ADD TABLE ...", rel_sync_cache_publication_cb callback function set replicate_valid to false, then it would validate the entry in get_rel_sync_entry function, and marked the pubactions to true. so it worked ok.
In the case of "ALTER PUBLICATION ... ADD SCHEMA ...", replicate_valid would not be set to false. Because of this, the pubactions were still false in get_rel_sync_entry function.
So I think the reason for it is similar to the one I reported before [1]/messages/by-id/OS0PR01MB61134B20314DE45795DD384CFB029@OS0PR01MB6113.jpnprd01.prod.outlook.com.
[1]: /messages/by-id/OS0PR01MB61134B20314DE45795DD384CFB029@OS0PR01MB6113.jpnprd01.prod.outlook.com
Regards
Tang
On Tue, Jun 29, 2021 at 8:55 AM tanghy.fnst@fujitsu.com <
tanghy.fnst@fujitsu.com> wrote:
On Friday, June 25, 2021 2:25 AM vignesh C <vignesh21@gmail.com>wrote:
Thanks for reporting these warnings, I have fixed this in the v8 patch
attached at [1].
[1] - https://www.postgresql.org/message-
id/CALDaNm044P_cds1OxZvFse5rE_qQfhbUg5MdtMgsa7t_bZGJdw%40mail.
gmail.comThanks for your patch. The warnings are fixed.
But I found an issue while using your V8 patch, which is similar to [1].
The case is as below:
Drop a schema from publication and refresh publication at subscriber,
then insert into publisher table, the inserts still replicated to
subscriber. The expect result is that the data is no longer replicated.
For example:
------publisher------
create schema s1;
create table s1.t1 (a int primary key);
create publication pub for schema s1;------subscriber------
create schema s1;
create table s1.t1 (a int primary key);
create subscription sub connection 'dbname=postgres port=5432'
publication pub;
------publisher------
insert into s1.t1 values (1);------subscriber------
postgres=# select * from s1.t1;
a
---
1
(1 row)------publisher------
alter publication pub drop schema s1;
insert into s1.t1 values (2);------subscriber------
postgres=# select * from s1.t1;
a
---
1
2
(2 rows)
Thanks for reporting this issue, the attached v9 patch fixes this issue.
This also fixes the other issue you reported at [1]/messages/by-id/OS0PR01MB61138278359AE713EA24140DFB019@OS0PR01MB6113.jpnprd01.prod.outlook.com.
[1]: /messages/by-id/OS0PR01MB61138278359AE713EA24140DFB019@OS0PR01MB6113.jpnprd01.prod.outlook.com
/messages/by-id/OS0PR01MB61138278359AE713EA24140DFB019@OS0PR01MB6113.jpnprd01.prod.outlook.com
Regards,
Vignesh
Attachments:
v9-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v9-0001-Added-schema-level-support-for-publication.patchDownload
From 0656ece9555712a2aa202f00d504a6f2333c137e Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 14 Jun 2021 10:23:23 +0530
Subject: [PATCH v9 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 150 ++++++++++
src/backend/catalog/pg_publication.c | 173 +++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 298 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 1 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 166 ++++++++++-
src/bin/pg_dump/pg_dump.h | 16 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 ++++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 42 +--
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 21 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 4 +
33 files changed, 1322 insertions(+), 166 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 69f9dd51a7..30026a967b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -67,8 +67,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 53392414f1..59600fc98d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3433,6 +3433,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3572,6 +3573,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0c37fc1d53..6326681371 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -179,6 +180,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1470,6 +1472,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2863,6 +2869,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..0066e0e9c6 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,51 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+ return address;
+ }
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2265,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2358,9 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ objnode = (Node *) list_make2(linitial(name), linitial(args));
+ break;
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3964,44 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4576,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5815,52 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 86e415af89..ee4ecfd111 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,76 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +377,45 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +454,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +500,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +526,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Publication *publication)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(publication->oid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(publication->pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +576,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +676,18 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..34cf049632 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c8e0..9f96b8b785 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(List *options,
@@ -141,6 +146,51 @@ parse_publication_options(List *options,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemoid;
+ List *search_path;
+ char *nspname;
+
+ switch (schema->schematype)
+ {
+ case SCHEMASPEC_CURRENT_SCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ nspname = get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemoid = get_namespace_oid(nspname, false);
+ break;
+
+ default:
+ schemoid = get_namespace_oid(schema->schemaname, false);
+ break;
+ }
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -213,6 +263,15 @@ CreatePublication(CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -226,6 +285,20 @@ CreatePublication(CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -252,6 +325,35 @@ CreatePublication(CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -311,7 +413,7 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
@@ -363,19 +465,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -427,11 +541,90 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, schemaoidlist)
+ {
+ Oid newschemaid = lfirst_oid(newlc);
+
+ if (newschemaid == oldschemaid)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +653,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -498,6 +693,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -608,7 +827,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -632,6 +851,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -666,6 +918,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -697,7 +983,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 6906714298..b108b641c5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 97a9725df7..8e2c0434dc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12138,6 +12139,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb24195438..32a062d0ce 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index abd5217ab1..8def9c54f9 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -16,6 +16,7 @@
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -960,6 +961,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1064,13 +1068,26 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ List *pubschemas = GetPublicationSchemas(pub->oid);
+
+ if (list_member_oid(pubschemas, schemaId))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
+
if (!publish)
{
bool ancestor_published = false;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 94fbf1aa19..8b4e0b8ed0 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 321152151d..f6b4f12648 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_trigger_d.h"
#include "catalog/pg_type_d.h"
#include "common/connect.h"
@@ -3974,6 +3975,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3988,25 +3990,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4024,6 +4038,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4048,6 +4063,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4090,7 +4106,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4157,6 +4173,102 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the table. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and process
+ * the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4244,6 +4356,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10405,6 +10555,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18620,6 +18773,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index ba9bc6ddd2..e9f85c819e 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -615,6 +616,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -628,6 +630,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -733,6 +747,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2abf255798..4a1bc5f7dd 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 15000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup("Publications:");
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 14 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ebd5aa41a..8cda579b5a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2634,15 +2643,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fd44081e74..08ec4c79f1 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -131,6 +131,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..0f8089d4da 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,27 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables
+ * publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 00e2e626e6..3c2a77d0b0 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,34 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(Publication *publication);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..dfeade5bf8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..169bdce07c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 77d176a934..2c1e9a3a31 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..28bf8daa64 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -85,10 +85,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -100,19 +100,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -131,10 +131,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -147,10 +147,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -170,10 +170,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -211,10 +211,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -258,10 +258,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -271,20 +271,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..fe5a038824 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 64c06cf952..da176df43e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -2035,6 +2036,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2326,6 +2328,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v9-0002-Tests-and-documentation-for-schema-level-support-.patchtext/x-patch; charset=US-ASCII; name=v9-0002-Tests-and-documentation-for-schema-level-support-.patchDownload
From d8997bcbf27cef4f684c676eb36af3b07b2d690c Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 21 Jun 2021 08:57:58 +0530
Subject: [PATCH v9 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 45 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 277 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 112 +++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++++++-
8 files changed, 716 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f517a7d4af..66fe960f92 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6171,6 +6176,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> and
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-altersubscription">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6238,6 +6265,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11274,9 +11362,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal> FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..532ca2ff62 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants change which schemas are part of the
+ publication. The <literal>SET SCHEMA</literal> clause will replace the list
+ of schemas in the publication with the specified one. The <literal>ADD
+ SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses will add and
+ remove one or more schemas from the publication. Note that adding schemas
+ to a publication that is already subscribed to will require a <literal>ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the subscribing side
+ in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..2529739dda 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,17 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future. If
+ schema is not specified, publication will be created for CURRENT_SCHEMA.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +165,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +183,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +235,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 28bf8daa64..e46e7cdd65 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -255,7 +255,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -287,11 +286,287 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..70a46172a5 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,122 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index dee5f5c30a..736fc16487 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA SCH2; INSERT INTO SCH2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Wednesday, June 30, 2021 7:43 PM vignesh C <vignesh21@gmail.com<mailto:vignesh21@gmail.com>> wrote:
Thanks for reporting this issue, the attached v9 patch fixes this issue. This also fixes the other issue you reported at [1].
Thanks for your patch. I confirmed that the two issues I reported has been fixed.
Regards
Tang
On Tue, Jun 22, 2021 at 10:11 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Tue, Jun 22, 2021 at 9:45 AM vignesh C <vignesh21@gmail.com> wrote:
I have removed the skip table patches to keep the focus on the main
patch, once this patch gets into committable shape, I will focus on
the skip table patch.IMO it's a good idea to start a new thread for the "skip table"
feature so that we can discuss it separately. If required you can
specify in that thread that the idea of the "skip table" can be
applied to the "add schema level support" feature.
Hi Vignesh,
I will find sometime to review the v9 patch set. I'm curious to know
whether the latest v9 patch set has the changes for the "skip table"
feature? Or is it being discussed separately?
Regards,
Bharath Rupireddy.
On Thu, Jul 1, 2021 at 5:43 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Tue, Jun 22, 2021 at 10:11 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Tue, Jun 22, 2021 at 9:45 AM vignesh C <vignesh21@gmail.com> wrote:
I have removed the skip table patches to keep the focus on the main
patch, once this patch gets into committable shape, I will focus on
the skip table patch.IMO it's a good idea to start a new thread for the "skip table"
feature so that we can discuss it separately. If required you can
specify in that thread that the idea of the "skip table" can be
applied to the "add schema level support" feature.Hi Vignesh,
I will find sometime to review the v9 patch set. I'm curious to know
whether the latest v9 patch set has the changes for the "skip table"
feature? Or is it being discussed separately?
The v9 patch does not include the "Skip table" feature, I'm planning
to focus on that once the current patch is stabilized.
Regards,
Vignesh
On Wednesday, June 30, 2021 7:43 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, the attached v9 patch fixes this issue. This also fixes the other issue you reported at [1].
A comment on v9:
src/bin/psql/describe.c
+ if (pset.sversion >= 15000)
I think it should be 150000.
Regards
Tang
On Fri, Jul 2, 2021 at 10:18 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Wednesday, June 30, 2021 7:43 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, the attached v9 patch fixes this issue. This also fixes the other issue you reported at [1].
A comment on v9:
src/bin/psql/describe.c
+ if (pset.sversion >= 15000)
I think it should be 150000.
Thanks for reporting this, I will fix this in the next version of the patch.
Regards,
Vignesh
On Wednesday, June 30, 2021 7:43 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, the attached v9 patch fixes this issue. This also fixes the other issue you reported at [1].
Hi,
I had a look at the patch, please consider following comments.
(1)
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ List *pubschemas = GetPublicationSchemas(pub->oid);
+
+ if (list_member_oid(pubschemas, schemaId))
+ {
It might be better use "else if" for the second check here.
Like: else if (pub->pubtype == PUBTYPE_SCHEMA)
Besides, we already have the {schemaoid, pubid} set here, it might be
better to scan the cache PUBLICATIONSCHEMAMAP instead of invoking
GetPublicationSchemas() which will scan the whole table.
(2)
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, schemaoidlist)
+ {
+ Oid newschemaid = lfirst_oid(newlc);
+
+ if (newschemaid == oldschemaid)
+ {
+ found = true;
+ break;
+ }
+ }
It seems we can use " if (list_member_oid(schemaoidlist, oldschemaid)) "
to replace the second foreach loop.
(3)
there are some testcases change in 0001 patch, it might be better move them
to 0002 patch.
(4)
+ case OBJECT_PUBLICATION_SCHEMA:
+ objnode = (Node *) list_make2(linitial(name), linitial(args));
+ break;
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
Does it looks better to merge these two switch cases ?
Like:
case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
best regards,
houzj
On Thursday, July 8, 2021 11:47 AM houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com> wrote
On Wednesday, June 30, 2021 7:43 PM vignesh C <vignesh21@gmail.com>
wrote:Thanks for reporting this issue, the attached v9 patch fixes this issue. This also
fixes the other issue you reported at [1].
Hi,
I had a look at the patch, please consider following comments.
Some more commets.
Currently, postgres caches publication actions info in the
RelationData::rd_pubactions, but after applying the patch, it seems
rd_pubactions is not initialized when using schema level publication.
It cound result in some unexpected behaviour when checking if command can be
executed with current replica identity.
----
CheckCmdReplicaIdentity
...
pubactions = GetRelationPublicationActions(rel);
if (cmd == CMD_UPDATE && pubactions->pubupdate)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot update table \"%s\" because it does not have a replica identity and publishes updates",
RelationGetRelationName(rel)),
errhint("To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.")));
----
Based on the above code, we access rd_pubactions from relcache in function
GetRelationPublicationActions(), if we don't have correct publication
information, we won't get the error in publication, instead we could lead to
the failure in subscription side.
To fix, I think (1) the patch can modify the function
GetRelationPublicationActions() to get the schema level publication related to
the relation, then merge the publication action to the rd_pubactions.
In addition, (2) it seems we also need to add the relcache invalidation code
about schema level publication when alter publication options.
I attached a rough code diff about (1) and (2), the diff is based on the v9 patchset.
I hope it can help fix the above issues.
Best regards,
Hou zhijie
Attachments:
fix_patchapplication/octet-stream; name=fix_patchDownload
From 7232e70d85dc4f34f99d68035ad36922c176771f Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Fri, 9 Jul 2021 08:49:25 +0800
Subject: [PATCH] fixdiff
---
src/backend/catalog/pg_publication.c | 43 ++++++++++++++++++++++++++++++----
src/backend/commands/publicationcmds.c | 10 ++++++--
src/backend/utils/cache/relcache.c | 4 ++++
src/include/commands/publicationcmds.h | 3 ++-
4 files changed, 53 insertions(+), 7 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index ee4ecfd..b1acc7a 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -416,6 +416,40 @@ GetPublicationSchemas(Oid pubid)
return result;
}
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -530,10 +564,10 @@ GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
* Gets the list of all relations published by FOR SCHEMA publication(s).
*/
List *
-GetAllSchemasPublicationRelations(Publication *publication)
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
{
List *result = NIL;
- List *pubschemalist = GetPublicationSchemas(publication->oid);
+ List *pubschemalist = GetPublicationSchemas(puboid);
ListCell *cell;
foreach(cell, pubschemalist)
@@ -541,7 +575,7 @@ GetAllSchemasPublicationRelations(Publication *publication)
Oid schemaOid = lfirst_oid(cell);
List *schemaRels = NIL;
- schemaRels = GetAllTablesPublicationRelations(publication->pubviaroot,
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
schemaOid);
result = list_concat(result, schemaRels);
}
@@ -685,7 +719,8 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
else if (publication->pubtype == PUBTYPE_SCHEMA)
- tables = GetAllSchemasPublicationRelations(publication);
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
else
tables = NIL;
funcctx->user_fctx = (void *) tables;
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9f96b8b..8e5d180 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -419,13 +419,19 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubtype == PUBTYPE_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e964aea..e610357 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5449,6 +5449,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5480,6 +5481,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 3c2a77d..b34efab 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -33,9 +33,10 @@ extern List *GetRelationPublications(Oid relid);
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
-extern List *GetAllSchemasPublicationRelations(Publication *publication);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
--
2.7.2.windows.1
On Fri, Jul 9, 2021 at 1:28 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Currently, postgres caches publication actions info in the
RelationData::rd_pubactions, but after applying the patch, it seems
rd_pubactions is not initialized when using schema level publication.It cound result in some unexpected behaviour when checking if command can be
executed with current replica identity.
While testing this patch, I'm finding that for a FOR SCHEMA
publication, UPDATEs and DELETEs on tables belonging to that schema
are not getting replicated (but INSERTs and TRUNCATEs are).
Could this be related to the issues that Hou-san has identified?
Regards,
Greg Nancarrow
Fujitsu Australia
On Fri, Jul 9, 2021 at 8:58 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Thursday, July 8, 2021 11:47 AM houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com> wrote
On Wednesday, June 30, 2021 7:43 PM vignesh C <vignesh21@gmail.com>
wrote:Thanks for reporting this issue, the attached v9 patch fixes this issue. This also
fixes the other issue you reported at [1].
Hi,
I had a look at the patch, please consider following comments.
Some more commets.
Currently, postgres caches publication actions info in the
RelationData::rd_pubactions, but after applying the patch, it seems
rd_pubactions is not initialized when using schema level publication.It cound result in some unexpected behaviour when checking if command can be
executed with current replica identity.----
CheckCmdReplicaIdentity
...
pubactions = GetRelationPublicationActions(rel);
if (cmd == CMD_UPDATE && pubactions->pubupdate)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot update table \"%s\" because it does not have a replica identity and publishes updates",
RelationGetRelationName(rel)),
errhint("To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.")));
----
Based on the above code, we access rd_pubactions from relcache in function
GetRelationPublicationActions(), if we don't have correct publication
information, we won't get the error in publication, instead we could lead to
the failure in subscription side.To fix, I think (1) the patch can modify the function
GetRelationPublicationActions() to get the schema level publication related to
the relation, then merge the publication action to the rd_pubactions.In addition, (2) it seems we also need to add the relcache invalidation code
about schema level publication when alter publication options.I attached a rough code diff about (1) and (2), the diff is based on the v9 patchset.
I hope it can help fix the above issues.
Thanks for identifying the issues and also providing the fix for it.
Those changes are required, I have taken your changes into my patch.
The Attached patch has the changes for the same.
Regards,
Vignesh
Attachments:
v10-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v10-0001-Added-schema-level-support-for-publication.patchDownload
From cb7cbd833f1370252ee0224a38ccb947c30cb93e Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 14 Jun 2021 10:23:23 +0530
Subject: [PATCH v10 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 148 ++++++++++
src/backend/catalog/pg_publication.c | 212 +++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 295 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 21 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 166 ++++++++++-
src/bin/pg_dump/pg_dump.h | 16 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 ++++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 42 +--
src/include/catalog/pg_publication_schema.h | 47 ++++
src/include/commands/publicationcmds.h | 22 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 4 +
33 files changed, 1361 insertions(+), 168 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 53392414f1..59600fc98d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3433,6 +3433,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3572,6 +3573,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0c37fc1d53..6326681371 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -179,6 +180,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1470,6 +1472,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2863,6 +2869,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..978420d33c 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,51 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+ return address;
+ }
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2265,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2358,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3962,44 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4574,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5813,52 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 86e415af89..65a5fff497 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,76 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +377,83 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets list of publication oids for publications marked as FOR SCHEMA.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationSchemaPsnspcidPspubidIndexId, true,
+ NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +492,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +538,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +564,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +614,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +714,19 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..34cf049632 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c8e0..af27aea37e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(List *options,
@@ -141,6 +146,51 @@ parse_publication_options(List *options,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemoid;
+ List *search_path;
+ char *nspname;
+
+ switch (schema->schematype)
+ {
+ case SCHEMASPEC_CURRENT_SCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ nspname = get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemoid = get_namespace_oid(nspname, false);
+ break;
+
+ default:
+ schemoid = get_namespace_oid(schema->schemaname, false);
+ break;
+ }
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -213,6 +263,15 @@ CreatePublication(CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -226,6 +285,20 @@ CreatePublication(CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -252,6 +325,35 @@ CreatePublication(CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -311,19 +413,25 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubtype == PUBTYPE_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
@@ -363,19 +471,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -427,11 +547,77 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+
+ if (!list_member_oid(schemaoidlist, oldschemaid))
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +646,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -498,6 +686,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -608,7 +820,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -632,6 +844,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -666,6 +911,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -697,7 +976,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 03dfd2e7fa..c17dafbb68 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12231,6 +12232,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb24195438..32a062d0ce 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index abd5217ab1..41cdc3353c 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -960,6 +962,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1064,12 +1069,26 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ Oid prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaId),
+ ObjectIdGetDatum(pub->oid));
+ if (OidIsValid(prid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 5dac9f0696..e6103575ea 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5448,6 +5449,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5479,6 +5481,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 321152151d..f6b4f12648 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_trigger_d.h"
#include "catalog/pg_type_d.h"
#include "common/connect.h"
@@ -3974,6 +3975,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3988,25 +3990,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4024,6 +4038,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4048,6 +4063,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4090,7 +4106,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4157,6 +4173,102 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the table. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and process
+ * the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4244,6 +4356,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10405,6 +10555,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18620,6 +18773,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index ba9bc6ddd2..e9f85c819e 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -615,6 +616,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -628,6 +630,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -733,6 +747,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2abf255798..856e242302 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup("Publications:");
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 14 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ebd5aa41a..8cda579b5a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2634,15 +2643,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fd44081e74..08ec4c79f1 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -131,6 +131,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..0f8089d4da 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,27 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables
+ * publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 00e2e626e6..b34efab6e3 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,35 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..dfeade5bf8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..169bdce07c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 77d176a934..2c1e9a3a31 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..28bf8daa64 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -85,10 +85,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -100,19 +100,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -131,10 +131,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -147,10 +147,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -170,10 +170,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -211,10 +211,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -258,10 +258,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -271,20 +271,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..fe5a038824 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9a0936ead1..e6a1e7637b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -2035,6 +2036,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2326,6 +2328,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v10-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v10-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From 3542a129adb821ba0f63319fd7e09b693fa1392a Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 21 Jun 2021 08:57:58 +0530
Subject: [PATCH v10 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 277 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 112 +++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++++++-
8 files changed, 715 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f517a7d4af..66fe960f92 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6171,6 +6176,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> and
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-altersubscription">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6238,6 +6265,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11274,9 +11362,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal> FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..532ca2ff62 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants change which schemas are part of the
+ publication. The <literal>SET SCHEMA</literal> clause will replace the list
+ of schemas in the publication with the specified one. The <literal>ADD
+ SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses will add and
+ remove one or more schemas from the publication. Note that adding schemas
+ to a publication that is already subscribed to will require a <literal>ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the subscribing side
+ in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..801842c96c 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 28bf8daa64..e46e7cdd65 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -255,7 +255,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -287,11 +286,287 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..70a46172a5 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,122 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index dee5f5c30a..736fc16487 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA SCH2; INSERT INTO SCH2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Fri, Jul 9, 2021 at 12:12 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Jul 9, 2021 at 1:28 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:Currently, postgres caches publication actions info in the
RelationData::rd_pubactions, but after applying the patch, it seems
rd_pubactions is not initialized when using schema level publication.It cound result in some unexpected behaviour when checking if command
can be
executed with current replica identity.
While testing this patch, I'm finding that for a FOR SCHEMA
publication, UPDATEs and DELETEs on tables belonging to that schema
are not getting replicated (but INSERTs and TRUNCATEs are).
Could this be related to the issues that Hou-san has identified?
Thanks for reporting this issue. I felt this issue is the same as the issue
which Hou San had reported. This issue is fixed in the v10 patch attached
at [1]/messages/by-id/CALDaNm2+tR+8R-sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm2+tR+8R-sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw@mail.gmail.com
/messages/by-id/CALDaNm2+tR+8R-sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw@mail.gmail.com
Regards,
Vignesh
On Thu, Jul 8, 2021 at 9:16 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Wednesday, June 30, 2021 7:43 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, the attached v9 patch fixes this issue. This also fixes the other issue you reported at [1].
Hi,
I had a look at the patch, please consider following comments.
(1) - if (pub->alltables) + if (pub->pubtype == PUBTYPE_ALLTABLES) { publish = true; if (pub->pubviaroot && am_partition) publish_as_relid = llast_oid(get_partition_ancestors(relid)); }+ if (pub->pubtype == PUBTYPE_SCHEMA) + { + Oid schemaId = get_rel_namespace(relid); + List *pubschemas = GetPublicationSchemas(pub->oid); + + if (list_member_oid(pubschemas, schemaId)) + {It might be better use "else if" for the second check here.
Like: else if (pub->pubtype == PUBTYPE_SCHEMA)Besides, we already have the {schemaoid, pubid} set here, it might be
better to scan the cache PUBLICATIONSCHEMAMAP instead of invoking
GetPublicationSchemas() which will scan the whole table.
Modified.
(2) + /* Identify which schemas should be dropped. */ + foreach(oldlc, oldschemaids) + { + Oid oldschemaid = lfirst_oid(oldlc); + ListCell *newlc; + bool found = false; + + foreach(newlc, schemaoidlist) + { + Oid newschemaid = lfirst_oid(newlc); + + if (newschemaid == oldschemaid) + { + found = true; + break; + } + }It seems we can use " if (list_member_oid(schemaoidlist, oldschemaid)) "
to replace the second foreach loop.
Modified.
(3)
there are some testcases change in 0001 patch, it might be better move them
to 0002 patch.
These changes are required to modify the existing tests. I kept it in
0001 so that 0001 patch can be committed independently. I think we can
keep the change as it is, I did not make any changes for this.
(4) + case OBJECT_PUBLICATION_SCHEMA: + objnode = (Node *) list_make2(linitial(name), linitial(args)); + break; case OBJECT_USER_MAPPING: objnode = (Node *) list_make2(linitial(name), linitial(args)); break;Does it looks better to merge these two switch cases ?
Like:
case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
Modified.
Thanks for the comments, these comments are fixed as part of the v10
patch attached at [1]/messages/by-id/CALDaNm2+tR+8R-sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm2+tR+8R-sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw@mail.gmail.com
Regards,
Vignesh
On Fri, Jul 2, 2021 at 10:18 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Wednesday, June 30, 2021 7:43 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, the attached v9 patch fixes this issue. This also fixes the other issue you reported at [1].
A comment on v9:
src/bin/psql/describe.c
+ if (pset.sversion >= 15000)
I think it should be 150000.
Thanks for reporting this issue, this issue is fixed in the v10 patch
attached at [1]/messages/by-id/CALDaNm2+tR+8R-sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm2+tR+8R-sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw@mail.gmail.com
Regards,
Vignesh
On Monday, July 12, 2021 5:36 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, this issue is fixed in the v10 patch
attached at [1].
[1] - /messages/by-id/CALDaNm2+tR+8R-
sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw%40mail.gmail.com
Thanks for fixing it.
By applying your V10 patch, I saw three problems, please have a look.
1. An issue about pg_dump.
When public schema was published, the publication was created in the output file, but public schema was not added to it. (Other schemas could be added as expected.)
I looked into it and found that selectDumpableNamespace function marks DUMP_COMPONENT_DEFINITION as needless when the schema is public, leading to schema public is ignored in getPublicationSchemas. So we'd better check whether schemas should be dumped in another way.
I tried to fix it with the following change, please have a look. (Maybe we also need to add some comments for it.)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f6b4f12648..a327d2568b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4206,7 +4206,8 @@ getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
* Ignore publication membership of schemas whose definitions are not
* to be dumped.
*/
- if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ if (!((nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+ || (strcmp(nsinfo->dobj.name, "public") == 0 && nsinfo->dobj.dump != DUMP_COMPONENT_NONE)))
continue;
pg_log_info("reading publication membership for schema \"%s\"",
2. improper behavior for system schema
I found I could create publication for system schema, such as pg_catalog. I think it's better to report an error message here, just like creating publication for system table is unallowed.
3. fix for dumpPublicationSchema
Should we add a declaration for it and add const decorations to the it's second parameter? Like other similar functions.
Regards
Tang
On Mon, Jul 12, 2021 at 7:24 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue. I felt this issue is the same as the issue which Hou San had reported. This issue is fixed in the v10 patch attached at [1].
[1] - /messages/by-id/CALDaNm2+tR+8R-sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw@mail.gmail.com
I did some testing and the issue that I reported does seem to be fixed
by the v10 patch.
I have some patch review comments for the v10 patch:
(1)
The following:
+ if (!OidIsValid(address.objectId))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\"
does not exist",
+ schemaname, pubname)));
+ return address;
+ }
+
+ return address;
could just be simplified to:
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+ }
+
+ return address;
(2) src/backend/catalog/objectaddress.c
I think there is a potential illegal memory access (psform->psnspcid)
in the case of "!missing_ok", as the tuple is released from the cache
on the previous line.
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psform->psnspcid);
+ break;
+ }
I think this should be:
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
There are two cases of this that need correction (see: case
OCLASS_PUBLICATION_SCHEMA).
(3) Incomplete function header comment
+ * makeSchemaSpec - Create a SchemaSpec with the given type
Should be:
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
(4) src/bin/psql/describe.c
Shouldn't the following comment say "version 15"?
+ /* Prior to version 14 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
(5) typedefs.list
I think you also need to add "Form_pg_publication_schema" to typedefs.list.
Regards,
Greg Nancarrow
Fujitsu Australia
On Tue, Jul 13, 2021 at 12:06 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Monday, July 12, 2021 5:36 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, this issue is fixed in the v10 patch
attached at [1].
[1] - /messages/by-id/CALDaNm2+tR+8R-
sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw%40mail.gmail.comThanks for fixing it.
By applying your V10 patch, I saw three problems, please have a look.
1. An issue about pg_dump.
When public schema was published, the publication was created in the output file, but public schema was not added to it. (Other schemas could be added as expected.)I looked into it and found that selectDumpableNamespace function marks DUMP_COMPONENT_DEFINITION as needless when the schema is public, leading to schema public is ignored in getPublicationSchemas. So we'd better check whether schemas should be dumped in another way.
I tried to fix it with the following change, please have a look. (Maybe we also need to add some comments for it.)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index f6b4f12648..a327d2568b 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4206,7 +4206,8 @@ getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas) * Ignore publication membership of schemas whose definitions are not * to be dumped. */ - if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)) + if (!((nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) + || (strcmp(nsinfo->dobj.name, "public") == 0 && nsinfo->dobj.dump != DUMP_COMPONENT_NONE))) continue;pg_log_info("reading publication membership for schema \"%s\"",
I felt it is intentionally done like that as the pubic schema is
created by default, hence it is not required to dump else we will get
errors while restoring. Thougths?
2. improper behavior for system schema
I found I could create publication for system schema, such as pg_catalog. I think it's better to report an error message here, just like creating publication for system table is unallowed.
Modified.
3. fix for dumpPublicationSchema
Should we add a declaration for it and add const decorations to the it's second parameter? Like other similar functions.
Modified to include const, declaration is not required as the function
definition is before function call, so not making this change.
Thanks for your comments, the attached v11 patch fixes the issues.
Regards,
Vignesh
Attachments:
v11-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v11-0001-Added-schema-level-support-for-publication.patchDownload
From f8350733a485f3fa7ede8f3277c5b704aeafb300 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Wed, 14 Jul 2021 10:58:35 +0530
Subject: [PATCH v11 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 148 ++++++++++
src/backend/catalog/pg_publication.c | 228 ++++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 295 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 21 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 166 ++++++++++-
src/bin/pg_dump/pg_dump.h | 16 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 ++++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 42 +--
src/include/catalog/pg_publication_schema.h | 47 ++++
src/include/commands/publicationcmds.h | 22 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +
33 files changed, 1378 insertions(+), 168 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 53392414f1..59600fc98d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3433,6 +3433,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3572,6 +3573,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0c37fc1d53..6326681371 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -179,6 +180,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1470,6 +1472,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2863,6 +2869,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..d477a3a6eb 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3958,46 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4572,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5811,54 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 86e415af89..6af3509bf7 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,92 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schema cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schema cannot be added to publications.")));
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +393,83 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets list of publication oids for publications marked as FOR SCHEMA.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationSchemaPsnspcidPspubidIndexId, true,
+ NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +508,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +554,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +580,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +630,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +730,19 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..34cf049632 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 95c253c8e0..af27aea37e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(List *options,
@@ -141,6 +146,51 @@ parse_publication_options(List *options,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemoid;
+ List *search_path;
+ char *nspname;
+
+ switch (schema->schematype)
+ {
+ case SCHEMASPEC_CURRENT_SCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ nspname = get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemoid = get_namespace_oid(nspname, false);
+ break;
+
+ default:
+ schemoid = get_namespace_oid(schema->schemaname, false);
+ break;
+ }
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -213,6 +263,15 @@ CreatePublication(CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -226,6 +285,20 @@ CreatePublication(CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -252,6 +325,35 @@ CreatePublication(CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -311,19 +413,25 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubtype == PUBTYPE_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
@@ -363,19 +471,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -427,11 +547,77 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+
+ if (!list_member_oid(schemaoidlist, oldschemaid))
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(AlterPublicationStmt *stmt)
@@ -460,6 +646,8 @@ AlterPublication(AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -498,6 +686,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -608,7 +820,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -632,6 +844,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -666,6 +911,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -697,7 +976,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dcf14b9dc0..93adcc6170 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12231,6 +12232,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb24195438..bcf5565139 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index e4314af13a..485db04a06 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1059,6 +1061,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1163,12 +1168,26 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ Oid prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaId),
+ ObjectIdGetDatum(pub->oid));
+ if (OidIsValid(prid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..2ec805eefe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5447,6 +5448,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5480,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 912144c43e..be5aea1e04 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"
@@ -3975,6 +3976,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3989,25 +3991,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4025,6 +4039,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4049,6 +4064,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4091,7 +4107,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4158,6 +4174,102 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the table. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and process
+ * the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4245,6 +4357,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10421,6 +10571,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18636,6 +18789,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index efb8c30e71..3012269930 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -615,6 +616,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -628,6 +630,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -734,6 +748,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ba658f731b..cdcb75c193 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup("Publications:");
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 15 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d6bf725971..c6227f95e2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2634,15 +2643,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fd44081e74..08ec4c79f1 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -131,6 +131,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..0f8089d4da 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,27 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables
+ * publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 00e2e626e6..b34efab6e3 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,35 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
extern void AlterPublication(AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f7b009ec43..4653f02624 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..169bdce07c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..4415d9cd76 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..28bf8daa64 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -28,20 +28,20 @@ ERROR: unrecognized "publish" value: "cluster"
CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
ERROR: conflicting or redundant options
\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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -85,10 +85,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -100,19 +100,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -131,10 +131,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -147,10 +147,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -170,10 +170,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -211,10 +211,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -258,10 +258,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -271,20 +271,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..fe5a038824 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..fb5daa49eb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,6 +2047,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2332,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v11-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v11-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From 5582575f7ad4eba6cf8808c168bd89a5df1db140 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 21 Jun 2021 08:57:58 +0530
Subject: [PATCH v11 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 281 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 115 +++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++++++-
8 files changed, 722 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0f5d25b948..6faf7a9f03 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6171,6 +6176,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> and
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-altersubscription">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6238,6 +6265,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11286,9 +11374,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal> FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..532ca2ff62 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants change which schemas are part of the
+ publication. The <literal>SET SCHEMA</literal> clause will replace the list
+ of schemas in the publication with the specified one. The <literal>ADD
+ SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses will add and
+ remove one or more schemas from the publication. Note that adding schemas
+ to a publication that is already subscribed to will require a <literal>ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the subscribing side
+ in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..801842c96c 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 28bf8daa64..922b84d2c4 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -255,7 +255,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -287,11 +286,291 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schema cannot be added to publications.
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..581813db06 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,125 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index dee5f5c30a..736fc16487 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA SCH2; INSERT INTO SCH2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Tue, Jul 13, 2021 at 2:22 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Mon, Jul 12, 2021 at 7:24 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue. I felt this issue is the same as the
issue which Hou San had reported. This issue is fixed in the v10 patch
attached at [1]/messages/by-id/CALDaNm1oZzaEsZC1W8MRNGZ6LWOayC54_UzyRV+nCh8w0yW74g@mail.gmail.com.
[1] -
/messages/by-id/CALDaNm2+tR+8R-sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw@mail.gmail.com
I did some testing and the issue that I reported does seem to be fixed
by the v10 patch.I have some patch review comments for the v10 patch:
(1)
The following:
+ if (!OidIsValid(address.objectId)) + { + if (!missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("publication schema \"%s\" in publication \"%s\" does not exist", + schemaname, pubname))); + return address; + } + + return address;could just be simplified to:
+ if (!OidIsValid(address.objectId) && !missing_ok) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("publication schema \"%s\" in publication \"%s\" does not
exist",
+ schemaname, pubname))); + } + + return address;
Modified
(2) src/backend/catalog/objectaddress.c
I think there is a potential illegal memory access (psform->psnspcid)
in the case of "!missing_ok", as the tuple is released from the cache
on the previous line.+ psform = (Form_pg_publication_schema) GETSTRUCT(tup); + pubname = get_publication_name(psform->pspubid, false); + nspname = get_namespace_name(psform->psnspcid); + if (!nspname) + { + pfree(pubname); + ReleaseSysCache(tup); + if (!missing_ok) + elog(ERROR, "cache lookup failed for schema %u", + psform->psnspcid); + break; + }I think this should be:
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup); + pubname = get_publication_name(psform->pspubid, false); + nspname = get_namespace_name(psform->psnspcid); + if (!nspname) + { + Oid psnspcid = psform->psnspcid; + + pfree(pubname); + ReleaseSysCache(tup); + if (!missing_ok) + elog(ERROR, "cache lookup failed for schema %u", + psnspcid); + break; + }There are two cases of this that need correction (see: case
OCLASS_PUBLICATION_SCHEMA).
Modified
(3) Incomplete function header comment
+ * makeSchemaSpec - Create a SchemaSpec with the given type
Should be:
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
Modified
(4) src/bin/psql/describe.c
Shouldn't the following comment say "version 15"?
+ /* Prior to version 14 check was based on all tables */ + if ((has_pubtype && pubtype == PUBTYPE_TABLE) || + (!has_pubtype && !puballtables))
Modified
(5) typedefs.list
I think you also need to add "Form_pg_publication_schema" to
typedefs.list.
Modified.
Thanks for the comments, these comments are fixed in the v11 patch posted
at [1]/messages/by-id/CALDaNm1oZzaEsZC1W8MRNGZ6LWOayC54_UzyRV+nCh8w0yW74g@mail.gmail.com.
[1]: /messages/by-id/CALDaNm1oZzaEsZC1W8MRNGZ6LWOayC54_UzyRV+nCh8w0yW74g@mail.gmail.com
/messages/by-id/CALDaNm1oZzaEsZC1W8MRNGZ6LWOayC54_UzyRV+nCh8w0yW74g@mail.gmail.com
Regards,
Vignesh
Wednesday, July 14, 2021 6:17 PM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Jul 13, 2021 at 12:06 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:On Monday, July 12, 2021 5:36 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, this issue is fixed in the v10
patch attached at [1].
[1] - /messages/by-id/CALDaNm2+tR+8R-
sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw%40mail.gmail.comThanks for fixing it.
By applying your V10 patch, I saw three problems, please have a look.
1. An issue about pg_dump.
When public schema was published, the publication was created in the
output file, but public schema was not added to it. (Other schemas
could be added as expected.)I looked into it and found that selectDumpableNamespace function marks
DUMP_COMPONENT_DEFINITION as needless when the schema is public,
leading to schema public is ignored in getPublicationSchemas. So we'd better
check whether schemas should be dumped in another way.I tried to fix it with the following change, please have a look.
(Maybe we also need to add some comments for it.)diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index f6b4f12648..a327d2568b 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4206,7 +4206,8 @@ getPublicationSchemas(Archive *fout,NamespaceInfo nspinfo[], int numSchemas)
* Ignore publication membership of schemas whose
definitions are not
* to be dumped.
*/
- if (!(nsinfo->dobj.dump &DUMP_COMPONENT_DEFINITION))
+ if (!((nsinfo->dobj.dump &
DUMP_COMPONENT_DEFINITION)
+ || (strcmp(nsinfo->dobj.name, "public") == 0 + && nsinfo->dobj.dump != DUMP_COMPONENT_NONE))) continue;pg_log_info("reading publication membership for schema
\"%s\"",I felt it is intentionally done like that as the pubic schema is created by default,
hence it is not required to dump else we will get errors while restoring.
Thougths?
Thanks for the new patches and I also looked at this issue.
For user defined schema and publication:
--------------------------
create schema s1;
create publication pub2 for SCHEMA s1;
--------------------------
pg_dump will only generate the following SQLs:
------pg_dump result------
CREATE PUBLICATION pub2 WITH (publish = 'insert, update, delete, truncate');
ALTER PUBLICATION pub2 ADD SCHEMA s1;
--------------------------
But for the public schema:
--------------------------
create publication pub for SCHEMA public;
--------------------------
pg_dump will only generate the following SQL:
------pg_dump result------
CREATE PUBLICATION pub WITH (publish = 'insert, update, delete, truncate');
--------------------------
It didn't generate SQL like "ALTER PUBLICATION pub ADD SCHEMA public;" which
means the public schema won't be published after restoring. So, I think we'd
better let the pg_dump generate the ADD SCHEMA public SQL. Thoughts ?
Best regards,
Hou zhijie
On Wed, Jul 14, 2021 at 8:17 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for your comments, the attached v11 patch fixes the issues.
Thanks for your work on this.
I have some minor review comments on the documentation:
(1) wrong link (uses altersubscription instead of alterpublication)
doc/src/sgml/catalogs.sgml
BEFORE:
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-altersubscription">
+ <command>ALTER PUBLICATION</command></link> then the publication type
AFTER:
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-alterpublication">
+ <command>ALTER PUBLICATION</command></link> then the publication type
(2) Improve wording and suggest "or" instead of "and"
doc/src/sgml/catalogs.sgml
BEFORE:
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> and
+ <literal>FOR SCHEMA</literal> option, then the publication will be
AFTER:
+ If a publication is created without specifying any of the
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or
+ <literal>FOR SCHEMA</literal> options, then the publication will be
(3) space at start of literal
doc/src/sgml/catalogs.sgml
+ and <literal> FOR SCHEMA</literal>, so for such publications there will be a
(4) Should say "variants of this command change ..." ?
+ The fourth, fifth and sixth variants change which schemas are part of the
Also, there seems to be an issue with ALTER PUBLICATION ... SET SCHEMA ...
(PubType is getting set to 'e' instead of 's'
test_pub=# create publication pub1;
CREATE PUBLICATION
test_pub=# create table myschema.test(i int);
CREATE TABLE
test_pub=# alter publication pub1 set schema myschema;
ALTER PUBLICATION
test_pub=# \dRp pub1
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates |
Via root | PubType
------+-------+------------+---------+---------+---------+-----------+----------+---------
pub1 | gregn | f | t | t | t | t |
f | e
(1 row)
test_pub=# alter publication pub1 add table test;
ALTER PUBLICATION
test_pub=# \dRp pub1
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates |
Via root | PubType
------+-------+------------+---------+---------+---------+-----------+----------+---------
pub1 | gregn | f | t | t | t | t |
f | t
(1 row)
When I use "ADD SCHEMA" instead of "SET SCHEMA" on an empty
publication, it seems OK.
Regards,
Greg Nancarrow
Fujitsu Australia
On Wed, Jul 14, 2021 at 6:25 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Wednesday, July 14, 2021 6:17 PM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Jul 13, 2021 at 12:06 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:On Monday, July 12, 2021 5:36 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, this issue is fixed in the v10
patch attached at [1].
[1] - /messages/by-id/CALDaNm2+tR+8R-
sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw%40mail.gmail.comThanks for fixing it.
By applying your V10 patch, I saw three problems, please have a look.
1. An issue about pg_dump.
When public schema was published, the publication was created in the
output file, but public schema was not added to it. (Other schemas
could be added as expected.)I looked into it and found that selectDumpableNamespace function marks
DUMP_COMPONENT_DEFINITION as needless when the schema is public,
leading to schema public is ignored in getPublicationSchemas. So we'd better
check whether schemas should be dumped in another way.I tried to fix it with the following change, please have a look.
(Maybe we also need to add some comments for it.)diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index f6b4f12648..a327d2568b 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4206,7 +4206,8 @@ getPublicationSchemas(Archive *fout,NamespaceInfo nspinfo[], int numSchemas)
* Ignore publication membership of schemas whose
definitions are not
* to be dumped.
*/
- if (!(nsinfo->dobj.dump &DUMP_COMPONENT_DEFINITION))
+ if (!((nsinfo->dobj.dump &
DUMP_COMPONENT_DEFINITION)
+ || (strcmp(nsinfo->dobj.name, "public") == 0 + && nsinfo->dobj.dump != DUMP_COMPONENT_NONE))) continue;pg_log_info("reading publication membership for schema
\"%s\"",I felt it is intentionally done like that as the pubic schema is created by default,
hence it is not required to dump else we will get errors while restoring.
Thougths?Thanks for the new patches and I also looked at this issue.
For user defined schema and publication:
--------------------------
create schema s1;
create publication pub2 for SCHEMA s1;
--------------------------pg_dump will only generate the following SQLs:
------pg_dump result------
CREATE PUBLICATION pub2 WITH (publish = 'insert, update, delete, truncate');
ALTER PUBLICATION pub2 ADD SCHEMA s1;
--------------------------But for the public schema:
--------------------------
create publication pub for SCHEMA public;
--------------------------pg_dump will only generate the following SQL:
------pg_dump result------
CREATE PUBLICATION pub WITH (publish = 'insert, update, delete, truncate');
--------------------------It didn't generate SQL like "ALTER PUBLICATION pub ADD SCHEMA public;" which
means the public schema won't be published after restoring. So, I think we'd
better let the pg_dump generate the ADD SCHEMA public SQL. Thoughts ?
Thanks for reporting this issue, this issue is fixed in the v12 patch attached.
Regards,
Vignesh
Attachments:
v12-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v12-0001-Added-schema-level-support-for-publication.patchDownload
From ed678e305a4851012ad8f3a0775c9475e009ff52 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Fri, 16 Jul 2021 12:43:17 +0530
Subject: [PATCH v12 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 148 ++++++++++
src/backend/catalog/pg_publication.c | 228 ++++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 300 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 21 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 159 ++++++++++-
src/bin/pg_dump/pg_dump.h | 16 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 ++++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 42 +--
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 22 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +
33 files changed, 1376 insertions(+), 168 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..d477a3a6eb 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3958,46 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4572,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5811,54 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 36bfff9706..5653d3e90b 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,92 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schema cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schema cannot be added to publications.")));
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +393,83 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets list of publication oids for publications marked as FOR SCHEMA.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationSchemaPsnspcidPspubidIndexId, true,
+ NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +508,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +554,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +580,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +630,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +730,19 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..34cf049632 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 12f9f8b697..039f49f1b0 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +143,51 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemoid;
+ List *search_path;
+ char *nspname;
+
+ switch (schema->schematype)
+ {
+ case SCHEMASPEC_CURRENT_SCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ nspname = get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemoid = get_namespace_oid(nspname, false);
+ break;
+
+ default:
+ schemoid = get_namespace_oid(schema->schemaname, false);
+ break;
+ }
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -211,6 +261,15 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -224,6 +283,20 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -250,6 +323,35 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -310,19 +412,25 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubtype == PUBTYPE_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
@@ -362,19 +470,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -426,11 +546,82 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+
+ if (!list_member_oid(schemaoidlist, oldschemaid))
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +650,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +690,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -607,7 +824,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -631,6 +848,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +915,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid prid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(prid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, prid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -696,7 +980,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 28b178f208..a9a0bdc56e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12227,6 +12228,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 10da5c5c51..6099cb14f3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index e4314af13a..485db04a06 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1059,6 +1061,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1163,12 +1168,26 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ Oid prid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaId),
+ ObjectIdGetDatum(pub->oid));
+ if (OidIsValid(prid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..2ec805eefe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5447,6 +5448,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5480,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 912144c43e..bfcfabe8d0 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"
@@ -3975,6 +3976,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3989,25 +3991,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4025,6 +4039,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4049,6 +4064,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4091,7 +4107,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4158,6 +4174,95 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubrinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the schema. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and process
+ * the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubrinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubrinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubrinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubrinfo[j].dobj);
+ pubrinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubrinfo[j].dobj.name = nsinfo->dobj.name;
+ pubrinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubrinfo[j].pubschema = nsinfo;
+ pubrinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4245,6 +4350,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10421,6 +10564,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18636,6 +18782,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index efb8c30e71..3012269930 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -615,6 +616,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -628,6 +630,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -734,6 +748,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ba658f731b..cdcb75c193 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup("Publications:");
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 15 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d6bf725971..c6227f95e2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2634,15 +2643,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..0f8089d4da 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,27 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables
+ * publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index efea01f2a9..f008fe9744 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,35 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f7b009ec43..4653f02624 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..169bdce07c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..4415d9cd76 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index b5b065a1b6..baf52dc947 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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -87,10 +87,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -102,19 +102,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -133,10 +133,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -149,10 +149,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -172,10 +172,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -213,10 +213,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -260,10 +260,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -273,20 +273,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..fe5a038824 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..fb5daa49eb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,6 +2047,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2332,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v12-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v12-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From c9ef3d8a741cb02c1572afff4cbf8f0a93b54c0f Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 21 Jun 2021 08:57:58 +0530
Subject: [PATCH v12 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 281 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 115 +++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++++++-
8 files changed, 722 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..93a2d8a364 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6169,6 +6174,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-alterpublication">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6263,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11364,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..c05029b9a6 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..801842c96c 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index baf52dc947..5b1743ce6e 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -257,7 +257,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +288,291 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schema cannot be added to publications.
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..581813db06 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,125 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index dee5f5c30a..736fc16487 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA SCH2; INSERT INTO SCH2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Fri, Jul 16, 2021 at 9:25 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Wed, Jul 14, 2021 at 8:17 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for your comments, the attached v11 patch fixes the issues.
Thanks for your work on this.
I have some minor review comments on the documentation:
(1) wrong link (uses altersubscription instead of alterpublication)
doc/src/sgml/catalogs.sgmlBEFORE:
+ created as an empty publication type. When a table or schema is
added to
+ the publication using <link linkend="sql-altersubscription"> + <command>ALTER PUBLICATION</command></link> then the publication
type
AFTER:
+ created as an empty publication type. When a table or schema is
added to
+ the publication using <link linkend="sql-alterpublication"> + <command>ALTER PUBLICATION</command></link> then the publication
type
Modified.
(2) Improve wording and suggest "or" instead of "and"
doc/src/sgml/catalogs.sgmlBEFORE: + If a publication is created without specifying any of + <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal>
and
+ <literal>FOR SCHEMA</literal> option, then the publication will be
AFTER: + If a publication is created without specifying any of the + <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or + <literal>FOR SCHEMA</literal> options, then the publication will
be
Modified.
(3) space at start of literal
doc/src/sgml/catalogs.sgml+ and <literal> FOR SCHEMA</literal>, so for such publications there
will be a
Modified.
(4) Should say "variants of this command change ..." ?
+ The fourth, fifth and sixth variants change which schemas are part of
the
Modified.
Also, there seems to be an issue with ALTER PUBLICATION ... SET SCHEMA ...
(PubType is getting set to 'e' instead of 's'test_pub=# create publication pub1;
CREATE PUBLICATION
test_pub=# create table myschema.test(i int);
CREATE TABLE
test_pub=# alter publication pub1 set schema myschema;
ALTER PUBLICATION
test_pub=# \dRp pub1
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates |
Via root | PubType
------+-------+------------+---------+---------+---------+-----------+----------+---------
pub1 | gregn | f | t | t | t | t |
f | e
(1 row)test_pub=# alter publication pub1 add table test;
ALTER PUBLICATION
test_pub=# \dRp pub1
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates |
Via root | PubType
------+-------+------------+---------+---------+---------+-----------+----------+---------
pub1 | gregn | f | t | t | t | t |
f | t
(1 row)When I use "ADD SCHEMA" instead of "SET SCHEMA" on an empty
publication, it seems OK.
Modified.
Thanks for the comments, these issues are fixed as part of the v12 patch
posted at [1]/messages/by-id/CALDaNm3V9ny5dJM8nofLGJ3zDuDG0gS2dX+AhDph--U5y+4VbQ@mail.gmail.com.
[1]: /messages/by-id/CALDaNm3V9ny5dJM8nofLGJ3zDuDG0gS2dX+AhDph--U5y+4VbQ@mail.gmail.com
/messages/by-id/CALDaNm3V9ny5dJM8nofLGJ3zDuDG0gS2dX+AhDph--U5y+4VbQ@mail.gmail.com
Regards,
Vignesh
On Friday, July 16, 2021 6:13 PM vignesh C <vignesh21@gmail.com<mailto:vignesh21@gmail.com>> wrote:
On Fri, Jul 16, 2021 at 9:25 AM Greg Nancarrow <mailto:gregn4422@gmail.com> wrote:
Also, there seems to be an issue with ALTER PUBLICATION ... SET SCHEMA ...
(PubType is getting set to 'e' instead of 's'
test_pub=# create publication pub1;
CREATE PUBLICATION
test_pub=# create table myschema.test(i int);
CREATE TABLE
test_pub=# alter publication pub1 set schema myschema;
ALTER PUBLICATION
test_pub=# \dRp pub1
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates |
Via root | PubType
------+-------+------------+---------+---------+---------+-----------+----------+---------
pub1 | gregn | f | t | t | t | t |
f | e
(1 row)
test_pub=# alter publication pub1 add table test;
ALTER PUBLICATION
test_pub=# \dRp pub1
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates |
Via root | PubType
------+-------+------------+---------+---------+---------+-----------+----------+---------
pub1 | gregn | f | t | t | t | t |
f | t
(1 row)
When I use "ADD SCHEMA" instead of "SET SCHEMA" on an empty
publication, it seems OK.
Modified.
Thanks for your patch. But there is a problem about "ALTER PUBLICATION … SET TABLE …", which is similar to the issue Greg reported at [1]/messages/by-id/CAJcOf-ddXvY=OFC54CshdMa1bswzFjG9qokjC0aFeiS=6CNRzw@mail.gmail.com.
For example:
postgres=# CREATE TABLE public.t1 (a int);
CREATE TABLE
postgres=# CREATE PUBLICATION pub1;
CREATE PUBLICATION
postgres=# ALTER PUBLICATION pub1 SET TABLE public.t1;
ALTER PUBLICATION
postgres=# \dRp
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | PubType
------+----------+------------+---------+---------+---------+-----------+----------+---------
pub1 | postgres | f | t | t | t | t | f | e
(1 row)
I think PubType in this case should be 't' instead of 'e'. Please have a look.
[1]: /messages/by-id/CAJcOf-ddXvY=OFC54CshdMa1bswzFjG9qokjC0aFeiS=6CNRzw@mail.gmail.com
Regards,
Tang
On Friday, July 16, 2021 6:10 PM vignesh C <vignesh21@gmail.com>
On Wed, Jul 14, 2021 at 6:25 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:Wednesday, July 14, 2021 6:17 PM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Jul 13, 2021 at 12:06 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:On Monday, July 12, 2021 5:36 PM vignesh C <vignesh21@gmail.com>
wrote:
Thanks for reporting this issue, this issue is fixed in the v10
patch attached at [1].
[1] - /messages/by-id/CALDaNm2+tR+8R-
sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw%40mail.gmail.comThanks for fixing it.
By applying your V10 patch, I saw three problems, please have a look.
1. An issue about pg_dump.
When public schema was published, the publication was created in the
output file, but public schema was not added to it. (Other schemas
could be added as expected.)I looked into it and found that selectDumpableNamespace function marks
DUMP_COMPONENT_DEFINITION as needless when the schema is public,
leading to schema public is ignored in getPublicationSchemas. So we'd better
check whether schemas should be dumped in another way.I tried to fix it with the following change, please have a look.
(Maybe we also need to add some comments for it.)diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index f6b4f12648..a327d2568b 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4206,7 +4206,8 @@ getPublicationSchemas(Archive *fout,NamespaceInfo nspinfo[], int numSchemas)
* Ignore publication membership of schemas whose
definitions are not
* to be dumped.
*/
- if (!(nsinfo->dobj.dump &DUMP_COMPONENT_DEFINITION))
+ if (!((nsinfo->dobj.dump &
DUMP_COMPONENT_DEFINITION)
+ || (strcmp(nsinfo->dobj.name, "public") == 0 + && nsinfo->dobj.dump != DUMP_COMPONENT_NONE))) continue;pg_log_info("reading publication membership for schema
\"%s\"",I felt it is intentionally done like that as the pubic schema is created by default,
hence it is not required to dump else we will get errors while restoring.
Thougths?Thanks for the new patches and I also looked at this issue.
For user defined schema and publication:
--------------------------
create schema s1;
create publication pub2 for SCHEMA s1;
--------------------------pg_dump will only generate the following SQLs:
------pg_dump result------
CREATE PUBLICATION pub2 WITH (publish = 'insert, update, delete, truncate');
ALTER PUBLICATION pub2 ADD SCHEMA s1;
--------------------------But for the public schema:
--------------------------
create publication pub for SCHEMA public;
--------------------------pg_dump will only generate the following SQL:
------pg_dump result------
CREATE PUBLICATION pub WITH (publish = 'insert, update, delete, truncate');
--------------------------It didn't generate SQL like "ALTER PUBLICATION pub ADD SCHEMA public;" which
means the public schema won't be published after restoring. So, I think we'd
better let the pg_dump generate the ADD SCHEMA public SQL. Thoughts ?Thanks for reporting this issue, this issue is fixed in the v12 patch attached.
I tested your v12 patch and found a problem in the following case.
Step 1:
postgres=# create schema s1;
CREATE SCHEMA
postgres=# create table s1.t1 (a int);
CREATE TABLE
postgres=# create publication pub_t for table s1.t1;
CREATE PUBLICATION
postgres=# create publication pub_s for schema s1;
CREATE PUBLICATION
Step 2:
pg_dump -N s1
I dumped and excluded schema s1, pg_dump generated the following SQL:
-------------------------------
ALTER PUBLICATION pub_s ADD SCHEMA s1;
I think it was not expected because SQL like "ALTER PUBLICATION pub_t ADD TABLE s1.t1" was not generated in my case. Thoughts?
Regards
Tang
On Fri, Jul 16, 2021 at 8:13 PM vignesh C <vignesh21@gmail.com> wrote:
Modified.
Thanks for the comments, these issues are fixed as part of the v12 patch posted at [1].
[1] - /messages/by-id/CALDaNm3V9ny5dJM8nofLGJ3zDuDG0gS2dX+AhDph--U5y+4VbQ@mail.gmail.com
There seems to be a problem with ALTER PUBLICATION ... SET TABLE ...
After that command, it still regards it as an empty (e) publication,
so I can then ALTER PUBLICATION ... ADD SCHEMA ...
e.g.
test_pub=# create schema myschema;
CREATE SCHEMA
test_pub=# CREATE TABLE myschema.test (key int, value text, data jsonb);
CREATE TABLE
test_pub=# create publication pub1;
CREATE PUBLICATION
test_pub=# \dRp+ pub1
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via
root | Pubtype
-------+------------+---------+---------+---------+-----------+----------+---------
gregn | f | t | t | t | t | f | e
(1 row)
test_pub=# alter publication pub1 set table myschema.test;
ALTER PUBLICATION
test_pub=# \dRp+ pub1
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via
root | Pubtype
-------+------------+---------+---------+---------+-----------+----------+---------
gregn | f | t | t | t | t | f | e
(1 row)
test_pub=# alter publication pub1 add schema myschema;
ALTER PUBLICATION
test_pub=# \dRp+ pub1
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via
root | Pubtype
-------+------------+---------+---------+---------+-----------+----------+---------
gregn | f | t | t | t | t | f | s
Schemas:
"myschema"
Regards,
Greg Nancarrow
Fujitsu Australia
On Mon, Jul 19, 2021 at 2:41 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Jul 16, 2021 at 8:13 PM vignesh C <vignesh21@gmail.com> wrote:
Modified.
Thanks for the comments, these issues are fixed as part of the v12 patch
posted at [1].
[1] -
/messages/by-id/CALDaNm3V9ny5dJM8nofLGJ3zDuDG0gS2dX+AhDph--U5y+4VbQ@mail.gmail.com
There seems to be a problem with ALTER PUBLICATION ... SET TABLE ...
After that command, it still regards it as an empty (e) publication,
so I can then ALTER PUBLICATION ... ADD SCHEMA ...
One issue here is that the code to update publication type is missing
in AlterPublicationTables for SET TABLE command.
More broadly, I am not clear about the behaviour of the patch when a
publication is created to publish only certain tables, and is later altered
to publish
a whole schema. I think such behaviour is legitimate. However,
AFAIU as per current code we can't update the publication type
from PUBTYPE_TABLE to PUBTYPE_SCHEMA.
I have some review comments as follows:
1.
In ConvertSchemaSpecListToOidList(List *schemas) function:
+ search_path = fetch_search_path(false);
+ nspname =
get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /*
recently-deleted namespace? */
+ ereport(ERROR,
+
errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema
has been selected"));
+
+ schemoid = get_namespace_oid(nspname,
false);
+ break;
The call get_namespace_oid() is perhaps not needed as fetch_search_path
already fetches oids and simply
doing Schema oid = liinital_oid(search_path)); should be enough.
2. In the same function should there be an if else condition block instead
of a switch case as
there are only two cases.
Thank you,
Rahila Syed
On Mon, Jul 19, 2021 at 8:43 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Friday, July 16, 2021 6:13 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Jul 16, 2021 at 9:25 AM Greg Nancarrow <mailto:gregn4422@gmail.com> wrote:
Also, there seems to be an issue with ALTER PUBLICATION ... SET SCHEMA ...
(PubType is getting set to 'e' instead of 's'
test_pub=# create publication pub1;
CREATE PUBLICATION
test_pub=# create table myschema.test(i int);
CREATE TABLE
test_pub=# alter publication pub1 set schema myschema;
ALTER PUBLICATION
test_pub=# \dRp pub1
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates |
Via root | PubType
------+-------+------------+---------+---------+---------+-----------+----------+---------
pub1 | gregn | f | t | t | t | t |
f | e
(1 row)
test_pub=# alter publication pub1 add table test;
ALTER PUBLICATION
test_pub=# \dRp pub1
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates |
Via root | PubType
------+-------+------------+---------+---------+---------+-----------+----------+---------
pub1 | gregn | f | t | t | t | t |
f | t
(1 row)
When I use "ADD SCHEMA" instead of "SET SCHEMA" on an empty
publication, it seems OK.
Modified.
Thanks for your patch. But there is a problem about "ALTER PUBLICATION … SET TABLE …", which is similar to the issue Greg reported at [1].
For example:
postgres=# CREATE TABLE public.t1 (a int);
CREATE TABLE
postgres=# CREATE PUBLICATION pub1;
CREATE PUBLICATION
postgres=# ALTER PUBLICATION pub1 SET TABLE public.t1;
ALTER PUBLICATION
postgres=# \dRp
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | PubType
------+----------+------------+---------+---------+---------+-----------+----------+---------
pub1 | postgres | f | t | t | t | t | f | e
(1 row)
I think PubType in this case should be 't' instead of 'e'. Please have a look.
Thanks for reporting this issue, this issue is fixed in the attached v13 patch.
I have changed relation name pg_publication_schema to
pg_publication_sch so that the names are in similar lines with
pg_publication_rel relation and similar changes were done for variable
names too.
Regards,
Vignesh
Attachments:
v13-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v13-0001-Added-schema-level-support-for-publication.patchDownload
From da9becf28cb736d365add8b05d27afd502e1c1a9 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Fri, 16 Jul 2021 12:43:17 +0530
Subject: [PATCH v13 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 148 ++++++++++
src/backend/catalog/pg_publication.c | 228 ++++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 299 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 22 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 170 ++++++++++-
src/bin/pg_dump/pg_dump.h | 17 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 ++++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 42 +--
src/include/catalog/pg_publication_sch.h | 47 +++
src/include/commands/publicationcmds.h | 22 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +
33 files changed, 1388 insertions(+), 168 deletions(-)
create mode 100644 src/include/catalog/pg_publication_sch.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..01f282368c 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_sch.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..0ec4a6e02d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCH:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCH:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..3203e7a8aa 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_sch.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchRelationId, /* OCLASS_PUBLICATION_SCH */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCH:
+ RemovePublicationSchById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchRelationId:
+ return OCLASS_PUBLICATION_SCH;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..cc16ac2d29 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_sch.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCH */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCH
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_sch(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCH:
+ address = get_object_address_publication_sch(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_sch(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHMAP, Anum_pg_publication_sch_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCH:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCH:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3958,46 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCH:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_sch psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCH,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_sch) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4572,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCH:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5811,54 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCH:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_sch psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCH,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_sch) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..c6a7705e12 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_sch.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,92 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_sch];
+ bool nulls[Natts_pg_publication_sch];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schema cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schema cannot be added to publications.")));
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchObjectIndexId,
+ Anum_pg_publication_sch_oid);
+ values[Anum_pg_publication_sch_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_sch_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_sch_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +393,83 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_sch_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_sch pubsch;
+
+ pubsch = (Form_pg_publication_sch) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets list of publication oids for publications marked as FOR SCHEMA.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_sch_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationSchPsnspcidPspubidIndexId, true,
+ NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_sch pubsch;
+
+ pubsch = (Form_pg_publication_sch) GETSTRUCT(tup);
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +508,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +554,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +580,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +630,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +730,19 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e6797c88f2 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCH:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..8c4b2ff0eb 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCH:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCH:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCH:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCH:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..5e7398478f 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_sch.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +143,46 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -211,6 +256,15 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -224,6 +278,20 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -250,6 +318,35 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -310,19 +407,25 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubtype == PUBTYPE_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
@@ -362,19 +465,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -421,16 +536,91 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PublicationAddTables(pubid, rels, true, stmt);
CloseTableList(delrels);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
}
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+
+ if (!list_member_oid(schemaoidlist, oldschemaid))
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +649,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +689,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCH, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -607,7 +823,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -631,6 +847,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +914,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHMAP,
+ Anum_pg_publication_sch_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -696,7 +979,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..0087f49718 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCH:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a16e749506..b0aa29d7b5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12227,6 +12228,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCH:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 10da5c5c51..6099cb14f3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index e4314af13a..c897830b89 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_sch.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1059,6 +1061,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1163,12 +1168,27 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ Oid psid = GetSysCacheOid2(PUBLICATIONSCHMAP,
+ Anum_pg_publication_sch_oid,
+ ObjectIdGetDatum(schemaId),
+ ObjectIdGetDatum(pub->oid));
+
+ if (OidIsValid(psid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..2ec805eefe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5447,6 +5448,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5480,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..195cdc5252 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_sch.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchRelationId, /* PUBLICATIONSCH */
+ PublicationSchObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_sch_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchRelationId, /* PUBLICATIONSCHMAP */
+ PublicationSchPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_sch_psnspcid,
+ Anum_pg_publication_sch_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 34b91bb226..c3c0104f11 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"
@@ -1655,9 +1656,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3975,6 +3980,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3989,25 +3995,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4025,6 +4043,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4049,6 +4068,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4091,7 +4111,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4158,6 +4178,102 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the schema. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_sch ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and
+ * process the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nsinfo->dobj.name;
+ pubsinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubsinfo[j].pubschema = nsinfo;
+ pubsinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4245,6 +4361,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchInfo *pubrinfo)
+{
+ NamespaceInfo *schemainfo = pubrinfo->pubschema;
+ PublicationInfo *pubinfo = pubrinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubrinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10470,6 +10624,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18718,6 +18875,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..281771d547 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -616,6 +618,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -629,6 +632,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ba658f731b..aa9b283edb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_sch ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_sch ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup("Publications:");
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 15 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_sch ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d6bf725971..c6227f95e2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2634,15 +2643,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..6f74d196ec 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCH, /* pg_publication_sch */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..0f8089d4da 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,27 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables
+ * publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_sch.h b/src/include/catalog/pg_publication_sch.h
new file mode 100644
index 0000000000..04451d3039
--- /dev/null
+++ b/src/include/catalog/pg_publication_sch.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_sch.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_sch)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_sch.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCH_H
+#define PG_PUBLICATION_SCH_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_sch_d.h"
+
+
+/* ----------------
+ * pg_publication_sch definition. cpp turns this into
+ * typedef struct FormData_pg_publication_sch
+ * ----------------
+ */
+CATALOG(pg_publication_sch,8901,PublicationSchRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_sch;
+
+/* ----------------
+ * Form_pg_publication_sch corresponds to a pointer to a tuple with
+ * the format of pg_publication_sch relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_sch *Form_pg_publication_sch;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_sch_oid_index, 8902, PublicationSchObjectIndexId, on pg_publication_sch using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_sch_psnspcid_pspubid_index, 8903, PublicationSchPsnspcidPspubidIndexId, on pg_publication_sch using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCH_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index efea01f2a9..92a306dc6c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,35 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f7b009ec43..4653f02624 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 947660a4b0..9ae9b3f8d8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCH,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..4415d9cd76 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..0eb6d6fa74 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCH,
+ PUBLICATIONSCHMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..23cb6658a4 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_sch {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_sch {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..736df15463 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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -87,10 +87,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -102,19 +102,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -133,10 +133,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -149,10 +149,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -172,10 +172,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -213,10 +213,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -260,10 +260,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -273,20 +273,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index a64f96e102..185483450e 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_sch|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..a669ece990 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_sch
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_sch
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,6 +2047,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2332,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v13-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v13-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From e2099326bb2b5940f1ed0f9c8ece99028f0e63d7 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 21 Jun 2021 08:57:58 +0530
Subject: [PATCH v13 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 281 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 115 +++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++++++-
8 files changed, 722 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..dcb8bdf8cd 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-sch"><structname>pg_publication_sch</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6169,6 +6174,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-alterpublication">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6263,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-sch">
+ <title><structname>pg_publication_sch</structname></title>
+
+ <indexterm zone="catalog-pg-publication-sch">
+ <primary>pg_publication_sch</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_sch</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_sch</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11364,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..c05029b9a6 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..801842c96c 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 736df15463..00030d49bc 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -257,7 +257,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +288,291 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_sch ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_sch ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_sch ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_sch ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_sch ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schema cannot be added to publications.
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..ece3f6acf6 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,125 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_sch ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_sch ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_sch ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_sch ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_sch ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index dee5f5c30a..736fc16487 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA SCH2; INSERT INTO SCH2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Mon, Jul 19, 2021 at 9:32 AM tanghy.fnst@fujitsu.com <
tanghy.fnst@fujitsu.com> wrote:
On Friday, July 16, 2021 6:10 PM vignesh C <vignesh21@gmail.com>
On Wed, Jul 14, 2021 at 6:25 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:Wednesday, July 14, 2021 6:17 PM vignesh C <vignesh21@gmail.com>
wrote:
On Tue, Jul 13, 2021 at 12:06 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:On Monday, July 12, 2021 5:36 PM vignesh C <vignesh21@gmail.com>
wrote:
Thanks for reporting this issue, this issue is fixed in the v10
patch attached at [1].
[1] - /messages/by-id/CALDaNm2+tR+8R-
sD1CSyMbZcZbkintZE-avefjsp7LCkm6HMmw%40mail.gmail.comThanks for fixing it.
By applying your V10 patch, I saw three problems, please have a
look.
1. An issue about pg_dump.
When public schema was published, the publication was created in
the
output file, but public schema was not added to it. (Other schemas
could be added as expected.)I looked into it and found that selectDumpableNamespace function
marks
DUMP_COMPONENT_DEFINITION as needless when the schema is public,
leading to schema public is ignored in getPublicationSchemas. So
we'd better
check whether schemas should be dumped in another way.
I tried to fix it with the following change, please have a look.
(Maybe we also need to add some comments for it.)diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index f6b4f12648..a327d2568b 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4206,7 +4206,8 @@ getPublicationSchemas(Archive *fout,NamespaceInfo nspinfo[], int numSchemas)
* Ignore publication membership of schemas whose
definitions are not
* to be dumped.
*/
- if (!(nsinfo->dobj.dump &DUMP_COMPONENT_DEFINITION))
+ if (!((nsinfo->dobj.dump &
DUMP_COMPONENT_DEFINITION)
+ || (strcmp(nsinfo->dobj.name, "public")
== 0
+ && nsinfo->dobj.dump != DUMP_COMPONENT_NONE)))
continue;pg_log_info("reading publication membership for
schema
\"%s\"",
I felt it is intentionally done like that as the pubic schema is
created by default,
hence it is not required to dump else we will get errors while
restoring.
Thougths?
Thanks for the new patches and I also looked at this issue.
For user defined schema and publication:
--------------------------
create schema s1;
create publication pub2 for SCHEMA s1;
--------------------------pg_dump will only generate the following SQLs:
------pg_dump result------
CREATE PUBLICATION pub2 WITH (publish = 'insert, update, delete,
truncate');
ALTER PUBLICATION pub2 ADD SCHEMA s1;
--------------------------But for the public schema:
--------------------------
create publication pub for SCHEMA public;
--------------------------pg_dump will only generate the following SQL:
------pg_dump result------
CREATE PUBLICATION pub WITH (publish = 'insert, update, delete,
truncate');
--------------------------
It didn't generate SQL like "ALTER PUBLICATION pub ADD SCHEMA
public;" which
means the public schema won't be published after restoring. So, I
think we'd
better let the pg_dump generate the ADD SCHEMA public SQL. Thoughts ?
Thanks for reporting this issue, this issue is fixed in the v12 patch
attached.
I tested your v12 patch and found a problem in the following case.
Step 1:
postgres=# create schema s1;
CREATE SCHEMA
postgres=# create table s1.t1 (a int);
CREATE TABLE
postgres=# create publication pub_t for table s1.t1;
CREATE PUBLICATION
postgres=# create publication pub_s for schema s1;
CREATE PUBLICATIONStep 2:
pg_dump -N s1I dumped and excluded schema s1, pg_dump generated the following SQL:
-------------------------------
ALTER PUBLICATION pub_s ADD SCHEMA s1;I think it was not expected because SQL like "ALTER PUBLICATION pub_t ADD
TABLE s1.t1" was not generated in my case. Thoughts?
Thanks for reporting this issue, this issue is fixed in the v13 patch
posted at [1]/messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com
[1]: /messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com
/messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com
Regards,
Vignesh
On Mon, Jul 19, 2021 at 2:41 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Jul 16, 2021 at 8:13 PM vignesh C <vignesh21@gmail.com> wrote:
Modified.
Thanks for the comments, these issues are fixed as part of the v12 patch posted at [1].
[1] - /messages/by-id/CALDaNm3V9ny5dJM8nofLGJ3zDuDG0gS2dX+AhDph--U5y+4VbQ@mail.gmail.comThere seems to be a problem with ALTER PUBLICATION ... SET TABLE ...
After that command, it still regards it as an empty (e) publication,
so I can then ALTER PUBLICATION ... ADD SCHEMA ...e.g.
test_pub=# create schema myschema;
CREATE SCHEMA
test_pub=# CREATE TABLE myschema.test (key int, value text, data jsonb);
CREATE TABLE
test_pub=# create publication pub1;
CREATE PUBLICATION
test_pub=# \dRp+ pub1
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via
root | Pubtype
-------+------------+---------+---------+---------+-----------+----------+---------
gregn | f | t | t | t | t | f | e
(1 row)test_pub=# alter publication pub1 set table myschema.test;
ALTER PUBLICATION
test_pub=# \dRp+ pub1
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via
root | Pubtype
-------+------------+---------+---------+---------+-----------+----------+---------
gregn | f | t | t | t | t | f | e
(1 row)test_pub=# alter publication pub1 add schema myschema;
ALTER PUBLICATION
test_pub=# \dRp+ pub1
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via
root | Pubtype
-------+------------+---------+---------+---------+-----------+----------+---------
gregn | f | t | t | t | t | f | s
Schemas:
"myschema"
Thanks for reporting this issue, this issue is fixed in the v13 patch
posted at [1]/messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com
[1]: /messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com
Regards,
Vignesh
On Wed, Jul 21, 2021 at 3:14 PM Rahila Syed <rahilasyed90@gmail.com> wrote:
On Mon, Jul 19, 2021 at 2:41 PM Greg Nancarrow <gregn4422@gmail.com>
wrote:
On Fri, Jul 16, 2021 at 8:13 PM vignesh C <vignesh21@gmail.com> wrote:
Modified.
Thanks for the comments, these issues are fixed as part of the v12
patch posted at [1]/messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com.
[1] -
/messages/by-id/CALDaNm3V9ny5dJM8nofLGJ3zDuDG0gS2dX+AhDph--U5y+4VbQ@mail.gmail.com
There seems to be a problem with ALTER PUBLICATION ... SET TABLE ...
After that command, it still regards it as an empty (e) publication,
so I can then ALTER PUBLICATION ... ADD SCHEMA ...One issue here is that the code to update publication type is missing
in AlterPublicationTables for SET TABLE command.
Modified.
More broadly, I am not clear about the behaviour of the patch when a
publication is created to publish only certain tables, and is later
altered to publish
a whole schema. I think such behaviour is legitimate. However,
AFAIU as per current code we can't update the publication type
from PUBTYPE_TABLE to PUBTYPE_SCHEMA.
I initially thought this might not be required for users, I have not made
any change for this, I will try to get a few more people's opinion on this
and then fix it if required.
I have some review comments as follows: 1. In ConvertSchemaSpecListToOidList(List *schemas) function: + search_path = fetch_search_path(false); + nspname =
get_namespace_name(linitial_oid(search_path));
+ if (nspname == NULL) /*
recently-deleted namespace? */
+ ereport(ERROR, +
errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no
schema has been selected"));
+ + schemoid = get_namespace_oid(nspname,
false);
+ break;
The call get_namespace_oid() is perhaps not needed as fetch_search_path
already fetches oids and simply
doing Schema oid = liinital_oid(search_path)); should be enough.
Modified
2. In the same function should there be an if else condition block
instead of a switch case as
there are only two cases.
Modified.
Thanks for the comments, these comments are fixed in the v13 patch posted
at [1]/messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com.
[1]: /messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com
/messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com
Regards,
Vignesh
From: vignesh C <vignesh21@gmail.com>
Sent: Thursday, July 22, 2021 1:38 AM
To: Rahila Syed <rahilasyed90@gmail.com>
Cc: Greg Nancarrow <gregn4422@gmail.com>; Tang, Haiying/唐 海英 <tanghy.fnst@fujitsu.com>; Ajin Cherian <itsajin@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>; Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Subject: Re: Added schema level support for publication.
On Wed, Jul 21, 2021 at 3:14 PM Rahila Syed <rahilasyed90@gmail.com<mailto:rahilasyed90@gmail.com>> wrote:
On Mon, Jul 19, 2021 at 2:41 PM Greg Nancarrow <gregn4422@gmail.com<mailto:gregn4422@gmail.com>> wrote:
On Fri, Jul 16, 2021 at 8:13 PM vignesh C <vignesh21@gmail.com<mailto:vignesh21@gmail.com>> wrote:
Modified.
Thanks for the comments, these issues are fixed as part of the v12 patch posted at [1].
[1] - /messages/by-id/CALDaNm3V9ny5dJM8nofLGJ3zDuDG0gS2dX+AhDph--U5y+4VbQ@mail.gmail.comThere seems to be a problem with ALTER PUBLICATION ... SET TABLE ...
After that command, it still regards it as an empty (e) publication,
so I can then ALTER PUBLICATION ... ADD SCHEMA ...One issue here is that the code to update publication type is missing
in AlterPublicationTables for SET TABLE command.
Modified.
More broadly, I am not clear about the behaviour of the patch when a
publication is created to publish only certain tables, and is later altered to publish
a whole schema. I think such behaviour is legitimate. However,
AFAIU as per current code we can't update the publication type
from PUBTYPE_TABLE to PUBTYPE_SCHEMA.
I initially thought this might not be required for users, I have not made any change for this, I will try to get a few more people's opinion on this and then fix it if required.
I have some review comments as follows: 1. In ConvertSchemaSpecListToOidList(List *schemas) function: + search_path = fetch_search_path(false); + nspname = get_namespace_name(linitial_oid(search_path)); + if (nspname == NULL) /* recently-deleted namespace? */ + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("no schema has been selected")); + + schemoid = get_namespace_oid(nspname, false); + break;The call get_namespace_oid() is perhaps not needed as fetch_search_path already fetches oids and simply
doing Schema oid = liinital_oid(search_path)); should be enough.
Modified
2. In the same function should there be an if else condition block instead of a switch case as
there are only two cases.
Modified.
Thanks for the comments, these comments are fixed in the v13 patch posted at [1]/messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com.
[1]: /messages/by-id/CALDaNm0=MaXyAok5iq_-DeWUd81vpdF47-MZbbrsd+zB2P6WwA@mail.gmail.com
Regards,
Vignesh
On, July 22, 2021 1:38 AM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Jul 21, 2021 at 3:14 PM Rahila Syed <mailto:rahilasyed90@gmail.com> wrote:
More broadly, I am not clear about the behaviour of the patch when a
publication is created to publish only certain tables, and is later altered to publish
a whole schema. I think such behaviour is legitimate. However,
AFAIU as per current code we can't update the publication type
from PUBTYPE_TABLE to PUBTYPE_SCHEMA.
I initially thought this might not be required for users, I have not made any
change for this, I will try to get a few more people's opinion on this and then fix it if required.
Currently, It's not allowed to ALTER a FOR TABLE PUBLICATION to a FOR ALL
TABLES PUBLICATION. So, I am not sure it's legitimate to ALTER a FOR TABLE
PUBLICATION to a FOR SCHEMA PUBLICATION. Personally, It sounds more like a
separate feature which can be discussed in a separate thread.
Best regards,
houzj
On July 22, 2021 1:30 AM vignesh C <vignesh21@gmail.com> wrote
I think PubType in this case should be 't' instead of 'e'. Please have a look.
Thanks for reporting this issue, this issue is fixed in the attached v13 patch.
I have changed relation name pg_publication_schema to pg_publication_sch
so that the names are in similar lines with pg_publication_rel relation and similar
changes were done for variable names too.
Hi,
Thanks for the new version patches.
I had a few comments.
1)
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
It seems we can combine these two function call.
like appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD SCHEMA %s;\n",
fmtId(pubrinfo->pubname),
fmtId(schemainfo->dobj.name));
2)
+ footers[0] = pstrdup("Publications:");
+
This word seems need to be translated.
footers[0] = pstrdup(_("Publications:"));
3)
I think it might be better to add a testcase to cover the issue
reported before [1]/messages/by-id/CAJcOf-dsKOYKmdrU5nwWeFoHvhiACbmw_KU=JQMEeDp6WwijqA@mail.gmail.com.
[1]: /messages/by-id/CAJcOf-dsKOYKmdrU5nwWeFoHvhiACbmw_KU=JQMEeDp6WwijqA@mail.gmail.com
4)
Personally, the new name pg_publication_sch is not very easy to understand.
(Maybe it's because I am not a native english speaker. If others feel ok,
please ignore this comment)
Best regards,
Houzj
On Thu, Jul 22, 2021 at 1:42 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Personally, the new name pg_publication_sch is not very easy to understand.
(Maybe it's because I am not a native english speaker. If others feel ok,
please ignore this comment)
I was actually thinking the same thing.
I prefer the full SCHEMA/schema, even for all the internal
variables/definitions which have been changed since the last patch
version.
I think Vignesh was trying to be consistent with pg_publication_rel
and pg_subscription_rel, but maybe "rel" is better understood to be an
abbreviation for "relation" than "sch" for "schema"?
Thoughts from others?
Regards,
Greg Nancarrow
Fujitsu Australia
On Thursday, July 22, 2021 1:30 AM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, this issue is fixed in the attached v13 patch.
I have changed relation name pg_publication_schema to
pg_publication_sch so that the names are in similar lines with
pg_publication_rel relation and similar changes were done for variable
names too.
Thanks for your fixing. The issue is fixed as you said.
After applying your V13 patch. I noticed that if I specify duplicate schema names when using "ALTER PUBLICATION ... SET SCHEMA ...", I would get the following error message:
postgres=# ALTER PUBLICATION pub1 SET SCHEMA s1,s1;
ERROR: duplicate key value violates unique constraint "pg_publication_sch_psnspcid_pspubid_index"
DETAIL: Key (psnspcid, pspubid)=(16406, 16405) already exists.
I think the error message is pretty hard to understand. Maybe we can do sth to improve this scenario.
Here is two proposal:
1. Don't report error message, just add some code to make the above command to be executed successfully,
just like "ALTER PUBLICATION ... SET TABLE ..." as follolws:
postgres=# ALTER PUBLICATION pub2 SET TABLE t1,t1;
ALTER PUBLICATION
postgres=# \dRp+ pub2
Publication pub2
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
----------+------------+---------+---------+---------+-----------+----------+---------
postgres | f | t | t | t | t | f | t
Tables:
"public.t1"
2. Report a easily understandable message like: Schema s1 specified more than once
Thoughts?
Regards
Tang
On Fri, Jul 23, 2021 at 10:56 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
After applying your V13 patch. I noticed that if I specify duplicate schema names when using "ALTER PUBLICATION ... SET SCHEMA ...", I would get the following error message:
postgres=# ALTER PUBLICATION pub1 SET SCHEMA s1,s1;
ERROR: duplicate key value violates unique constraint "pg_publication_sch_psnspcid_pspubid_index"
DETAIL: Key (psnspcid, pspubid)=(16406, 16405) already exists.
That definitely seems to be a bug, since "ALTER PUBLICATION ... SET
TABLE ..." ignores duplicates and there is no ERROR.
"CREATE PUBLICATION ... SET SCHEMA s1, s1;" and "ALTER PUBLICATION ...
ADD SCHEMA s1, s1;" also give the same kind of error.
Regards,
Greg Nancarrow
Fujitsu Australia
On Thu, Jul 22, 2021 at 9:12 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On July 22, 2021 1:30 AM vignesh C <vignesh21@gmail.com> wrote
I think PubType in this case should be 't' instead of 'e'. Please have a look.
Thanks for reporting this issue, this issue is fixed in the attached v13 patch.
I have changed relation name pg_publication_schema to pg_publication_sch
so that the names are in similar lines with pg_publication_rel relation and similar
changes were done for variable names too.Hi,
Thanks for the new version patches.
I had a few comments.1) + + appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubrinfo->pubname)); + appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name)); +It seems we can combine these two function call.
like appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD SCHEMA %s;\n",
fmtId(pubrinfo->pubname),
fmtId(schemainfo->dobj.name));
Modified.
2) + footers[0] = pstrdup("Publications:"); +This word seems need to be translated.
footers[0] = pstrdup(_("Publications:"));
Modified
3)
I think it might be better to add a testcase to cover the issue
reported before [1].[1] /messages/by-id/CAJcOf-dsKOYKmdrU5nwWeFoHvhiACbmw_KU=JQMEeDp6WwijqA@mail.gmail.com
Added
4)
Personally, the new name pg_publication_sch is not very easy to understand.
(Maybe it's because I am not a native english speaker. If others feel ok,
please ignore this comment)
I have changed it to pg_publication_schema as earlier, as Greg also
felt the other name was better.
Thanks for the comments, the attached v14 patch has the fixes for the same.
Regards,
Vignesh
Attachments:
v14-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v14-0001-Added-schema-level-support-for-publication.patchDownload
From 22ccf066df2a1fe7cf51016aeb5462eff020dfa1 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Fri, 16 Jul 2021 12:43:17 +0530
Subject: [PATCH v14 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 148 ++++++++++
src/backend/catalog/pg_publication.c | 228 ++++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 303 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 22 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 171 ++++++++++-
src/bin/pg_dump/pg_dump.h | 17 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 +++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 42 +--
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 22 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +
33 files changed, 1393 insertions(+), 168 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..f390415467 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3958,46 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4572,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5811,54 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..10f598e346 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,92 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schema cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schema cannot be added to publications.")));
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +393,83 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets list of publication oids for publications marked as FOR SCHEMA.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationSchemaPsnspcidPspubidIndexId, true,
+ NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +508,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +554,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +580,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +630,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +730,19 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..34cf049632 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..19b80e34bf 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +143,50 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1". */
+ if (list_member_oid(schemaoidlist, schemaoid))
+ continue;
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -211,6 +260,15 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -224,6 +282,20 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -250,6 +322,35 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -310,19 +411,25 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubtype == PUBTYPE_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
@@ -362,19 +469,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -421,16 +540,91 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PublicationAddTables(pubid, rels, true, stmt);
CloseTableList(delrels);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
}
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+
+ if (!list_member_oid(schemaoidlist, oldschemaid))
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +653,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +693,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -607,7 +827,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -631,6 +851,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +918,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -696,7 +983,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a16e749506..4373da14b1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12227,6 +12228,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 10da5c5c51..6099cb14f3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index e4314af13a..283f9d2224 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1059,6 +1061,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1163,12 +1168,27 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ Oid psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaId),
+ ObjectIdGetDatum(pub->oid));
+
+ if (OidIsValid(psid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..2ec805eefe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5447,6 +5448,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5480,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 34b91bb226..7be202f6e8 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"
@@ -1655,9 +1656,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3975,6 +3980,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3989,25 +3995,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4025,6 +4043,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4049,6 +4068,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4091,7 +4111,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4158,6 +4178,102 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the schema. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and
+ * process the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nsinfo->dobj.name;
+ pubsinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubsinfo[j].pubschema = nsinfo;
+ pubsinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4245,6 +4361,45 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubsinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query,
+ "ALTER PUBLICATION %s ADD SCHEMA %s;\n",
+ fmtId(pubsinfo->pubname), fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10470,6 +10625,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18718,6 +18876,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..37554cee63 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -616,6 +618,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -629,6 +632,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ba658f731b..bfb7164465 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 15 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d6bf725971..c6227f95e2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2634,15 +2643,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..0f8089d4da 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,27 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables
+ * publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index efea01f2a9..f008fe9744 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,35 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "nodes/parsenodes.h"
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f7b009ec43..4653f02624 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 947660a4b0..163c7bb723 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..4415d9cd76 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..736df15463 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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -87,10 +87,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -102,19 +102,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -133,10 +133,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -149,10 +149,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -172,10 +172,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -213,10 +213,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -260,10 +260,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -273,20 +273,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index a64f96e102..aff1886124 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..fb5daa49eb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,6 +2047,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2332,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.25.1
v14-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v14-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From 2db79e50f3f99e71745655f1bdfe5778f95bbbb1 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 21 Jun 2021 08:57:58 +0530
Subject: [PATCH v14 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 +++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 314 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 130 +++++++-
src/test/subscription/t/001_rep_changes.pl | 150 ++++++++-
8 files changed, 770 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..93a2d8a364 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6169,6 +6174,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-alterpublication">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6263,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11364,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..c05029b9a6 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..801842c96c 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 736df15463..c7d3bc9be2 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -257,7 +257,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +288,324 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schema cannot be added to publications.
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+-- Alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | e
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..0c4f1a8edb 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,140 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index dee5f5c30a..736fc16487 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA SCH2; INSERT INTO SCH2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.25.1
On Thu, Jul 22, 2021 at 9:38 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Thu, Jul 22, 2021 at 1:42 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:Personally, the new name pg_publication_sch is not very easy to
understand.
(Maybe it's because I am not a native english speaker. If others feel
ok,
please ignore this comment)
I was actually thinking the same thing.
I prefer the full SCHEMA/schema, even for all the internal
variables/definitions which have been changed since the last patch
version.
I think Vignesh was trying to be consistent with pg_publication_rel
and pg_subscription_rel, but maybe "rel" is better understood to be an
abbreviation for "relation" than "sch" for "schema"?
Thoughts from others?
I have changed it to pg_pubication_schema as earlier as both you and Houzj
San felt pg_publication_schema was better. This is fixed in the v14 patch
attached at [1]/messages/by-id/CALDaNm3DTj535ezfmm8QHLOtOkcHF2ZcCfSjfR=VbTbLZXFRsA@mail.gmail.com.
[1]: /messages/by-id/CALDaNm3DTj535ezfmm8QHLOtOkcHF2ZcCfSjfR=VbTbLZXFRsA@mail.gmail.com
/messages/by-id/CALDaNm3DTj535ezfmm8QHLOtOkcHF2ZcCfSjfR=VbTbLZXFRsA@mail.gmail.com
Regards,
Vignesh
On Fri, Jul 23, 2021 at 6:26 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Thursday, July 22, 2021 1:30 AM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this issue, this issue is fixed in the attached v13 patch.
I have changed relation name pg_publication_schema to
pg_publication_sch so that the names are in similar lines with
pg_publication_rel relation and similar changes were done for variable
names too.Thanks for your fixing. The issue is fixed as you said.
After applying your V13 patch. I noticed that if I specify duplicate schema names when using "ALTER PUBLICATION ... SET SCHEMA ...", I would get the following error message:
postgres=# ALTER PUBLICATION pub1 SET SCHEMA s1,s1;
ERROR: duplicate key value violates unique constraint "pg_publication_sch_psnspcid_pspubid_index"
DETAIL: Key (psnspcid, pspubid)=(16406, 16405) already exists.I think the error message is pretty hard to understand. Maybe we can do sth to improve this scenario.
Here is two proposal:
1. Don't report error message, just add some code to make the above command to be executed successfully,
just like "ALTER PUBLICATION ... SET TABLE ..." as follolws:postgres=# ALTER PUBLICATION pub2 SET TABLE t1,t1;
ALTER PUBLICATION
postgres=# \dRp+ pub2
Publication pub2
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
----------+------------+---------+---------+---------+-----------+----------+---------
postgres | f | t | t | t | t | f | t
Tables:
"public.t1"2. Report a easily understandable message like: Schema s1 specified more than once
I have changed it to not report any error, this issue is fixed in the
v14 patch attached at [1]/messages/by-id/CALDaNm3DTj535ezfmm8QHLOtOkcHF2ZcCfSjfR=VbTbLZXFRsA@mail.gmail.com.
[1]: /messages/by-id/CALDaNm3DTj535ezfmm8QHLOtOkcHF2ZcCfSjfR=VbTbLZXFRsA@mail.gmail.com
Regards,
Vignesh
On Friday, July 23, 2021 8:18 PM vignesh C <vignesh21@gmail.com> wrote:
I have changed it to not report any error, this issue is fixed in the
v14 patch attached at [1].
[1] - https://www.postgresql.org/message-
id/CALDaNm3DTj535ezfmm8QHLOtOkcHF2ZcCfSjfR%3DVbTbLZXFRsA%40mail.g
mail.com
Thanks for your new patch. But there's a conflict when apply patch v14 on the latest HEAD (it seems caused by commit #678f5448c2d869), please rebase it.
Besides, I tested your patch on old HEAD and confirmed that the issue I reported was fixed.
Regards
Tang
On Fri, Jul 23, 2021 at 10:16 PM vignesh C <vignesh21@gmail.com> wrote:
I have changed it to pg_pubication_schema as earlier as both you and Houzj San felt pg_publication_schema was better. This is fixed in the v14 patch attached at [1].
[1] - /messages/by-id/CALDaNm3DTj535ezfmm8QHLOtOkcHF2ZcCfSjfR=VbTbLZXFRsA@mail.gmail.com
Thanks, I think it looks better.
On another issue, there seems to be problems with pg_dump support for
FOR SCHEMA publications.
For example:
CREATE PUBLICATION p FOR SCHEMA myschema;
pg_dump is dumping:
CREATE PUBLICATION p WITH (publish = 'insert, update, delete, truncate');
...
ALTER PUBLICATION p ADD SCHEMA p;
Obviously that last line should instead be "ALTER PUBLICATION p ADD
SCHEMA myschema;"
I think the bug is caused by the following:
+ appendPQExpBuffer(query,
+ "ALTER PUBLICATION %s ADD SCHEMA %s;\n",
+ fmtId(pubsinfo->pubname), fmtId(schemainfo->dobj.name));
The comment for fmtId() says:
* Note that the returned string must be used before calling fmtId again,
* since we re-use the same return buffer each time.
So I think there was a reason why the ALTER PUBLICATION and ADD SCHEMA
were previously formatted separately (and so should NOT have been
combined).
It should be restored to how it was in the previous patch version.
Regards,
Greg Nancarrow
Fujitsu Australia
On Mon, Jul 26, 2021 at 8:17 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Jul 23, 2021 at 10:16 PM vignesh C <vignesh21@gmail.com> wrote:
I have changed it to pg_pubication_schema as earlier as both you and Houzj San felt pg_publication_schema was better. This is fixed in the v14 patch attached at [1].
[1] - /messages/by-id/CALDaNm3DTj535ezfmm8QHLOtOkcHF2ZcCfSjfR=VbTbLZXFRsA@mail.gmail.comThanks, I think it looks better.
On another issue, there seems to be problems with pg_dump support for
FOR SCHEMA publications.For example:
CREATE PUBLICATION p FOR SCHEMA myschema;
pg_dump is dumping:
CREATE PUBLICATION p WITH (publish = 'insert, update, delete, truncate');
...
ALTER PUBLICATION p ADD SCHEMA p;Obviously that last line should instead be "ALTER PUBLICATION p ADD
SCHEMA myschema;"I think the bug is caused by the following:
+ appendPQExpBuffer(query, + "ALTER PUBLICATION %s ADD SCHEMA %s;\n", + fmtId(pubsinfo->pubname), fmtId(schemainfo->dobj.name));The comment for fmtId() says:
* Note that the returned string must be used before calling fmtId again,
* since we re-use the same return buffer each time.So I think there was a reason why the ALTER PUBLICATION and ADD SCHEMA
were previously formatted separately (and so should NOT have been
combined).
It should be restored to how it was in the previous patch version.
Thanks for the comment, this is modified in the v15 patch attached.
Regards,
Vignesh
Attachments:
v15-0001-Added-schema-level-support-for-publication.patchapplication/x-patch; name=v15-0001-Added-schema-level-support-for-publication.patchDownload
From 9916c2453206a3c40bd9013fe6265129e469e7fb Mon Sep 17 00:00:00 2001
From: Vigneshwaran c <vignesh21@gmail.com>
Date: Mon, 26 Jul 2021 09:25:22 +0530
Subject: [PATCH v15 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 148 ++++++++++
src/backend/catalog/pg_publication.c | 228 ++++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 303 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 22 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 170 ++++++++++-
src/bin/pg_dump/pg_dump.h | 17 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 +++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 42 +--
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 22 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +
33 files changed, 1392 insertions(+), 168 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..f390415467 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache. */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3958,46 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4572,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5811,54 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..10f598e346 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,92 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schema cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schema cannot be added to publications.")));
+
+ /* Form a tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog. */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table. */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +393,83 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets list of publication oids for publications marked as FOR SCHEMA.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema. */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationSchemaPsnspcidPspubidIndexId, true,
+ NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +508,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +554,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +580,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +630,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +730,19 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..34cf049632 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..19b80e34bf 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +143,50 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1". */
+ if (list_member_oid(schemaoidlist, schemaoid))
+ continue;
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -211,6 +260,15 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -224,6 +282,20 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -250,6 +322,35 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple. */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog. */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -310,19 +411,25 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubtype == PUBTYPE_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
@@ -362,19 +469,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -421,16 +540,91 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PublicationAddTables(pubid, rels, true, stmt);
CloseTableList(delrels);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
}
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list. */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped. */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+
+ if (!list_member_oid(schemaoidlist, oldschemaid))
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them. */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +653,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +693,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -607,7 +827,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
foreach(lc, rels)
{
@@ -631,6 +851,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser. */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +918,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -696,7 +983,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a16e749506..4373da14b1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12227,6 +12228,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 10da5c5c51..6099cb14f3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index e4314af13a..283f9d2224 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1059,6 +1061,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1163,12 +1168,27 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ Oid psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaId),
+ ObjectIdGetDatum(pub->oid));
+
+ if (OidIsValid(psid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..2ec805eefe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5447,6 +5448,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5480,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..773f038b24 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publciation schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..52449521d3 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"
@@ -1630,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3950,6 +3955,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3964,25 +3970,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4000,6 +4018,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4024,6 +4043,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4066,7 +4086,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4133,6 +4153,102 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the schema. */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and
+ * process the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nsinfo->dobj.name;
+ pubsinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubsinfo[j].pubschema = nsinfo;
+ pubsinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4220,6 +4336,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubsinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubsinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10445,6 +10599,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18693,6 +18850,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..37554cee63 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -616,6 +618,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -629,6 +632,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ba658f731b..bfb7164465 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL)
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 15 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d6bf725971..c6227f95e2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2634,15 +2643,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..0f8089d4da 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,27 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables
+ * publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..76c10f2b3c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,35 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "parser/parse_node.h"
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f7b009ec43..4653f02624 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 947660a4b0..163c7bb723 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA.
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* Type of this rolespec */
+ char *schemaname; /* filled only for ROLESPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..4415d9cd76 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..736df15463 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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -87,10 +87,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -102,19 +102,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -133,10 +133,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -149,10 +149,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -172,10 +172,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -213,10 +213,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -260,10 +260,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -273,20 +273,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index a64f96e102..aff1886124 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..fb5daa49eb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,6 +2047,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2332,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v15-0002-Tests-and-documentation-for-schema-level-support.patchapplication/x-patch; name=v15-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From 533ec8bed2a1392fe0cc764ed5e5e12e1d4ee0e4 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 21 Jun 2021 08:57:58 +0530
Subject: [PATCH v15 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 +++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 314 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 130 +++++++-
src/test/subscription/t/001_rep_changes.pl | 150 ++++++++-
8 files changed, 770 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..93a2d8a364 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6169,6 +6174,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-alterpublication">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6263,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11364,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..c05029b9a6 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..801842c96c 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 736df15463..c7d3bc9be2 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -257,7 +257,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +288,324 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+--- Check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schema cannot be added to publications.
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- Alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+-- Alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | e
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..0c4f1a8edb 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -148,7 +148,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +168,140 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- CREATE publication with schema
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+--- Check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+--- Check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+--- Check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+
+--- Check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- Dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- Renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- Alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- Drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- Alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- Alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index dee5f5c30a..736fc16487 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data shsould be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO SCH2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA SCH2; INSERT INTO SCH2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.30.2
On Mon, Jul 26, 2021 at 7:41 AM tanghy.fnst@fujitsu.com <
tanghy.fnst@fujitsu.com> wrote:
On Friday, July 23, 2021 8:18 PM vignesh C <vignesh21@gmail.com> wrote:
I have changed it to not report any error, this issue is fixed in the
v14 patch attached at [1].
[1] - https://www.postgresql.org/message-
id/CALDaNm3DTj535ezfmm8QHLOtOkcHF2ZcCfSjfR%3DVbTbLZXFRsA%40mail.g
mail.comThanks for your new patch. But there's a conflict when apply patch v14 on
the latest HEAD (it seems caused by commit #678f5448c2d869), please rebase
it.
Thanks for reporting it, it is rebased at the v15 patch attached at [1]/messages/by-id/CALDaNm27LRWF9ney=cVeD-0jc2+J5Y0wNQhighZB=Aat4VbNBA@mail.gmail.com.
[1]: /messages/by-id/CALDaNm27LRWF9ney=cVeD-0jc2+J5Y0wNQhighZB=Aat4VbNBA@mail.gmail.com
/messages/by-id/CALDaNm27LRWF9ney=cVeD-0jc2+J5Y0wNQhighZB=Aat4VbNBA@mail.gmail.com
Regards,
Vignesh
On Mon, Jul 26, 2021 at 3:21 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comment, this is modified in the v15 patch attached.
I have several minor review comments.
(1) src/backend/catalog/objectaddress.c
Should start comment sentences with an uppercase letter, for consistency.
+ /* fetch publication name and schema oid from input list */
I also notice that some 1-sentence comments end with "." (full-stop)
and others don't. It seems to alternate all over the place, and so is
quite noticeable.
Unfortunately, it already seems to be like this in much of the code
that this patch patches.
Ideally (at least my personal preference is) 1-sentence comments
should not end with a ".".
(2) src/backend/catalog/pg_publication.c
errdetail message
I think the following should say "Temporary schemas ..." (since the
existing error message for tables says "System tables cannot be added
to publications.").
+ errdetail("Temporary schema cannot be added to publications.")));
(3) src/backend/commands/publicationcmds.c
PublicationAddTables
I think that the Assert below is not correct (i.e. not restrictive enough).
Although the condition passes, it is allowing, for example,
stmt->for_all_tables==true if stmt->schemas==NIL, and that doesn't
seem to be correct.
I suggest the following change:
BEFORE:
+ Assert(!stmt || !stmt->for_all_tables || !stmt->schemas);
AFTER:
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->schemas));
(4) src/backend/commands/publicationcmds.c
PublicationAddSchemas
Similarly, I think that the Assert below is not restrictive enough,
and think it should be changed:
BEFORE:
+ Assert(!stmt || !stmt->for_all_tables || !stmt->tables);
AFTER:
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->tables));
(5) src/bin/pg_dump/common.c
Spelling mistake.
BEFORE:
+ pg_log_info("reading publciation schemas");
AFTER:
+ pg_log_info("reading publication schemas");
Regards,
Greg Nancarrow
Fujitsu Australia
On Monday, July 26, 2021 1:25 PM vignesh C <vignesh21@gmail.com<mailto:vignesh21@gmail.com>> wrote:
On Mon, Jul 26, 2021 at 7:41 AM tanghy.fnst@fujitsu.com<mailto:tanghy.fnst@fujitsu.com> <tanghy.fnst@fujitsu.com<mailto:tanghy.fnst@fujitsu.com>> wrote:
On Friday, July 23, 2021 8:18 PM vignesh C <vignesh21@gmail.com<mailto:vignesh21@gmail.com>> wrote:
I have changed it to not report any error, this issue is fixed in the
v14 patch attached at [1].
id/CALDaNm3DTj535ezfmm8QHLOtOkcHF2ZcCfSjfR%3DVbTbLZXFRsA%40mail.g
mail.com
Thanks for your new patch. But there's a conflict when apply patch v14 on the latest HEAD (it seems caused by commit #678f5448c2d869), please rebase it.
Thanks for reporting it, it is rebased at the v15 patch attached at [1].
[1] - /messages/by-id/CALDaNm27LRWF9ney=cVeD-0jc2+J5Y0wNQhighZB=Aat4VbNBA@mail.gmail.com
Thanks for your new patch. I applied your patch and it succeeded.
Here are some comments on the tests in your patch. The attached file included changes about the comments, please have a look.
1. src/test/regress/sql/publication.sql
There are some existing tests to verify that we can't add table to ‘for all tables publication', should we add some tests about adding schema to ‘for all tables publication’ / ‘for table publication’?
I added some cases in the attached file.
Besides, the following existing comment seems not suitable. It uses 'SET' but the comment says 'add'. And since we can add table or schema, I think we should point out what we add is table, thoughts?
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
2. src/test/subscription/t/001_rep_changes.pl
2.1
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
There are two spaces between "INTO" and "sch1.tab1".
2.2
Most of publication names are lowercase, and some of them are uppercase. I think it will be better if all of them are lowercase.
I modified them in the attached file.
2.3
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
Should it be 'check subscription relation status is not yet dropped on subscriber' here?
2.4
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
There is only one publication, so the comment here should be 'Drop publication as we don't need it anymore'.
3.
There are some existing test cases about publication for table and publication for all tables in 002_pg_dump.pl, so I think we could add some test cases about publication for schema.
I tried to add some cases in the attached file.
Regards
Tang
Attachments:
test_patchapplication/octet-stream; name=test_patchDownload
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c5d8915be8..73aa9435fb 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2301,6 +2301,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2337,6 +2346,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 0c4f1a8edb..e6a52959c0 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,35 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 736fc16487..2cc84a3635 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -325,8 +325,8 @@ $result = $node_subscriber->safe_psql('postgres',
is($result, qq(10|1|10), 'check rows on subscriber catchup');
# Insert some data into few tables and verify that inserted data is replicated.
-$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
-$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
$node_publisher->wait_for_catchup('tap_sub_schema');
@@ -339,8 +339,8 @@ is($result, qq(20|1|20), 'check rows on subscriber catchup');
# Create new table in the publication schema, verify that subscriber does not get
# the new table data in the subscriber before refresh.
-$node_publisher->safe_psql('postgres', "CREATE TABLE SCH1.tab3 AS SELECT generate_series(1,10) AS a");
-$node_subscriber->safe_psql('postgres', "CREATE TABLE SCH1.tab3(a INT)");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
$node_publisher->wait_for_catchup('tap_sub_schema');
$result = $node_subscriber->safe_psql('postgres',
"SELECT count(*) FROM sch1.tab3");
@@ -355,7 +355,7 @@ $node_subscriber->safe_psql('postgres',
$node_subscriber->poll_query_until('postgres', $synced_query)
or die "Timed out while waiting for subscriber to synchronize data";
-$node_publisher->safe_psql('postgres', "INSERT INTO SCH1.tab3 VALUES(11)");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
$node_publisher->wait_for_catchup('tap_sub_schema');
$result = $node_subscriber->safe_psql('postgres',
"SELECT count(*), min(a), max(a) FROM sch1.tab3");
@@ -363,8 +363,8 @@ is($result, qq(11|1|11), 'check rows on subscriber catchup');
# Set the schema of a publication schema table to a non publication schema and
# verify that inserted data is not reflected by the subscriber.
-$node_publisher->safe_psql('postgres', "ALTER TABLE SCH1.tab3 SET SCHEMA SCH3");
-$node_publisher->safe_psql('postgres', "INSERT INTO SCH3.tab3 VALUES(11)");
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
$node_publisher->wait_for_catchup('tap_sub_schema');
$result = $node_subscriber->safe_psql('postgres',
"SELECT count(*), min(a), max(a) FROM sch1.tab3");
@@ -374,7 +374,7 @@ is($result, qq(11|1|11), 'check rows on subscriber catchup');
$result = $node_subscriber->safe_psql('postgres',
"SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
is($result, qq(5),
- 'check subscription relation status was dropped on subscriber');
+ 'check subscription relation status is not yet dropped on subscriber');
$node_subscriber->safe_psql('postgres',
"ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
$result = $node_subscriber->safe_psql('postgres',
@@ -384,7 +384,7 @@ is($result, qq(4),
# Drop table from the publication schema, verify that subscriber removes the
# table entry after refresh.
-$node_publisher->safe_psql('postgres', "DROP TABLE SCH1.tab2");
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
$node_publisher->wait_for_catchup('tap_sub_schema');
$result = $node_subscriber->safe_psql('postgres',
"SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
@@ -403,7 +403,7 @@ is($result, qq(3),
# Drop schema from publication, verify that the inserts are not published after
# dropping the schema from publication. Here 2nd insert should not be
# published.
-$node_publisher->safe_psql('postgres', "INSERT INTO SCH2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA SCH2; INSERT INTO SCH2.tab1 values(22)");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
$node_publisher->wait_for_catchup('tap_sub_schema');
$result = $node_subscriber->safe_psql('postgres',
"SELECT count(*), min(a), max(a) FROM sch2.tab1");
@@ -412,7 +412,7 @@ is($result, qq(21|1|21), 'check rows on subscriber catchup');
# Drop subscription as we don't need it anymore
$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
-# Drop publications as we don't need them anymore
+# Drop publication as we don't need it anymore
$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
# Clean up the tables on both publisher and subscriber as we don't need them
On Tue, Jul 27, 2021 at 12:21 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote
Thanks for your new patch. I applied your patch and it succeeded.
Here are some comments on the tests in your patch. The attached file included changes about the comments, please have a look.
1. src/test/regress/sql/publication.sql
There are some existing tests to verify that we can't add table to ‘for all tables publication', should we add some tests about adding schema to ‘for all tables publication’ / ‘for table publication’?
I added some cases in the attached file.
I have taken the changes.
Besides, the following existing comment seems not suitable. It uses 'SET' but the comment says 'add'. And since we can add table or schema, I think we should point out what we add is table, thoughts?
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
Since this is in base code, you might want to post a patch on a separate thread.
2. src/test/subscription/t/001_rep_changes.pl
2.1
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
There are two spaces between "INTO" and "sch1.tab1".
I have taken the changes.
2.2
Most of publication names are lowercase, and some of them are uppercase. I think it will be better if all of them are lowercase.
I modified them in the attached file.
I have taken the changes.
2.3
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status was dropped on subscriber');
Should it be 'check subscription relation status is not yet dropped on subscriber' here?
I have taken the changes.
2.4
+# Drop publications as we don't need them anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
There is only one publication, so the comment here should be 'Drop publication as we don't need it anymore'.
I have taken the changes.
3.
There are some existing test cases about publication for table and publication for all tables in 002_pg_dump.pl, so I think we could add some test cases about publication for schema.
I tried to add some cases in the attached file.
Thanks for the patch, I have merged the changes. Attached v16 patch
has the fixes for the same.
Regards,
Vignesh
Attachments:
v16-0001-Added-schema-level-support-for-publication.patchapplication/x-patch; name=v16-0001-Added-schema-level-support-for-publication.patchDownload
From 1ce21afbab9a0f74ecd670c34b3d3cfbc21fc593 Mon Sep 17 00:00:00 2001
From: Vigneshwaran c <vignesh21@gmail.com>
Date: Mon, 26 Jul 2021 09:25:22 +0530
Subject: [PATCH v16 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 148 ++++++++++
src/backend/catalog/pg_publication.c | 228 ++++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 303 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 22 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 170 ++++++++++-
src/bin/pg_dump/pg_dump.h | 17 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 +++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 41 +--
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 22 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +
33 files changed, 1391 insertions(+), 168 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..35d7d4fcff 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* Fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3958,46 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4572,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5811,54 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..26a907bd17 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,92 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schemas cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schemas cannot be added to publications.")));
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +393,83 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets list of publication oids for publications marked as FOR SCHEMA.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationSchemaPsnspcidPspubidIndexId, true,
+ NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +508,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +554,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +580,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +630,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +730,19 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9c31c9e763..34cf049632 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2131,6 +2133,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2213,6 +2216,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..9cf2c4e725 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +143,50 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ if (list_member_oid(schemaoidlist, schemaoid))
+ continue;
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -211,6 +260,15 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -224,6 +282,20 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -250,6 +322,35 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -310,19 +411,25 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubtype == PUBTYPE_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
@@ -362,19 +469,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -421,16 +540,91 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PublicationAddTables(pubid, rels, true, stmt);
CloseTableList(delrels);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
}
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+
+ if (!list_member_oid(schemaoidlist, oldschemaid))
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +653,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +693,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -607,7 +827,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->schemas));
foreach(lc, rels)
{
@@ -631,6 +851,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->tables));
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +918,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -696,7 +983,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a16e749506..4373da14b1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12227,6 +12228,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 10da5c5c51..6099cb14f3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9583,45 +9585,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9633,6 +9658,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9667,6 +9697,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16613,6 +16667,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index e4314af13a..283f9d2224 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1059,6 +1061,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1163,12 +1168,27 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ Oid psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaId),
+ ObjectIdGetDatum(pub->oid));
+
+ if (OidIsValid(psid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..2ec805eefe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5447,6 +5448,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5480,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..3e1f3cda09 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..afa03452b5 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"
@@ -1630,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3950,6 +3955,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3964,25 +3970,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4000,6 +4018,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4024,6 +4043,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4066,7 +4086,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4133,6 +4153,102 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the schema */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and
+ * process the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nsinfo->dobj.name;
+ pubsinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubsinfo[j].pubschema = nsinfo;
+ pubsinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4220,6 +4336,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubsinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubsinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10445,6 +10599,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18693,6 +18850,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..37554cee63 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -616,6 +618,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -629,6 +632,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8333558bda..5e2b7d43cb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 15 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d6bf725971..c6227f95e2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2634,15 +2643,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..b55d5205b1 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,26 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..76c10f2b3c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,35 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "parser/parse_node.h"
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f7b009ec43..4653f02624 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 947660a4b0..dd7e60105d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* type of this schemaspec */
+ char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3645,6 +3664,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..4415d9cd76 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..736df15463 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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -87,10 +87,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -102,19 +102,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -133,10 +133,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -149,10 +149,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -172,10 +172,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -213,10 +213,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -260,10 +260,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -273,20 +273,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..3b4f62025f 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -141,6 +141,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..fb5daa49eb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,6 +2047,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2332,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v16-0002-Tests-and-documentation-for-schema-level-support.patchapplication/x-patch; name=v16-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From f44b08e5b1f915027b2470908024525430e814a6 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh21@gmail.com>
Date: Mon, 21 Jun 2021 08:57:58 +0530
Subject: [PATCH v16 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 358 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 159 +++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++++-
9 files changed, 871 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..93a2d8a364 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6169,6 +6174,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-alterpublication">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6263,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11364,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..c05029b9a6 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..801842c96c 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c5d8915be8..73aa9435fb 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2301,6 +2301,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2337,6 +2346,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 736df15463..d8d7006f24 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,48 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +136,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -257,7 +299,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +330,324 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schemas cannot be added to publications.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+-- alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | e
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..33dbdf7bed 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,39 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -148,7 +175,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +195,140 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index dee5f5c30a..e22c2833a6 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.30.2
On Tue, Jul 27, 2021 at 5:11 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Mon, Jul 26, 2021 at 3:21 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comment, this is modified in the v15 patch attached.
I have several minor review comments.
(1) src/backend/catalog/objectaddress.c
Should start comment sentences with an uppercase letter, for consistency.+ /* fetch publication name and schema oid from input list */
I also notice that some 1-sentence comments end with "." (full-stop)
and others don't. It seems to alternate all over the place, and so is
quite noticeable.
Unfortunately, it already seems to be like this in much of the code
that this patch patches.
Ideally (at least my personal preference is) 1-sentence comments
should not end with a ".".
Modified.
(2) src/backend/catalog/pg_publication.c
errdetail messageI think the following should say "Temporary schemas ..." (since the
existing error message for tables says "System tables cannot be added
to publications.").+ errdetail("Temporary schema cannot be added to publications.")));
Modified.
(3) src/backend/commands/publicationcmds.c
PublicationAddTablesI think that the Assert below is not correct (i.e. not restrictive
enough).
Although the condition passes, it is allowing, for example,
stmt->for_all_tables==true if stmt->schemas==NIL, and that doesn't
seem to be correct.
I suggest the following change:BEFORE: + Assert(!stmt || !stmt->for_all_tables || !stmt->schemas); AFTER: + Assert(!stmt || (!stmt->for_all_tables && !stmt->schemas));
Modified.
(4) src/backend/commands/publicationcmds.c
PublicationAddSchemasSimilarly, I think that the Assert below is not restrictive enough,
and think it should be changed:BEFORE: + Assert(!stmt || !stmt->for_all_tables || !stmt->tables); AFTER: + Assert(!stmt || (!stmt->for_all_tables && !stmt->tables));
Modified.
(5) src/bin/pg_dump/common.c
Spelling mistake.
BEFORE: + pg_log_info("reading publciation schemas"); AFTER: + pg_log_info("reading publication schemas");
Modified.
Thanks for the comments, the comments are fixed in the v16 patch attached
at [1]/messages/by-id/CALDaNm2LgV5XcLF80rJ60NwnjKpZj==LxJpO4W2TG2G5XmUtDA@mail.gmail.com.
[1]: /messages/by-id/CALDaNm2LgV5XcLF80rJ60NwnjKpZj==LxJpO4W2TG2G5XmUtDA@mail.gmail.com
/messages/by-id/CALDaNm2LgV5XcLF80rJ60NwnjKpZj==LxJpO4W2TG2G5XmUtDA@mail.gmail.com
Regards,
Vignesh
On July 28, 2021 6:44 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the patch, I have merged the changes. Attached v16 patch has the
fixes for the same.
Thanks for the new version patches.
Here are a few comments:
1)
+ /* Identify which schemas should be dropped */
+ foreach(oldlc, oldschemaids)
+ {
+ Oid oldschemaid = lfirst_oid(oldlc);
+
+ if (!list_member_oid(schemaoidlist, oldschemaid))
+ delschemas = lappend_oid(delschemas, oldschemaid);
+ }
+
We can use list_difference here to simplify the code.
2)
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ if (list_member_oid(schemaoidlist, schemaoid))
+ continue;
+
+ schemaoidlist = lappend_oid(schemaoidlist, schemaoid);
It might be more concise to use list_append_unique_oid() here.
3)
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+the schema "production":
+<programlisting>
The second line seems not aligned.
After:
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
4)
+ resetPQExpBuffer(query);
+
+ /* Get the publication membership for the schema */
+ appendPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
It seems we can use printfPQExpBuffer() here which is a convenience routine
that does the same thing as resetPQExpBuffer() followed by appendPQExpBuffer().
Best regards,
Houzj
On Fri, Jul 30, 2021 at 12:12 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On July 28, 2021 6:44 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the patch, I have merged the changes. Attached v16 patch has the
fixes for the same.Thanks for the new version patches.
Here are a few comments:1)
+ /* Identify which schemas should be dropped */ + foreach(oldlc, oldschemaids) + { + Oid oldschemaid = lfirst_oid(oldlc); + + if (!list_member_oid(schemaoidlist, oldschemaid)) + delschemas = lappend_oid(delschemas, oldschemaid); + } +We can use list_difference here to simplify the code.
Modified.
2)
+ + /* Filter out duplicates if user specifies "sch1, sch1" */ + if (list_member_oid(schemaoidlist, schemaoid)) + continue; + + schemaoidlist = lappend_oid(schemaoidlist, schemaoid);It might be more concise to use list_append_unique_oid() here.
Modified.
3)
+ <para> + Create a publication that publishes all changes for all the tables present in +the schema "production": +<programlisting>The second line seems not aligned. After: + <para> + Create a publication that publishes all changes for all the tables present in + the schema "production": +<programlisting>
Modified.
4)
+ resetPQExpBuffer(query); + + /* Get the publication membership for the schema */ + appendPQExpBuffer(query, + "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid " + "FROM pg_publication_schema ps, pg_publication p " + "WHERE ps.psnspcid = '%u' " + "AND p.oid = ps.pspubid", + nsinfo->dobj.catId.oid);It seems we can use printfPQExpBuffer() here which is a convenience routine
that does the same thing as resetPQExpBuffer() followed by appendPQExpBuffer().
Modified.
Thanks for the comments, attached v17 patches has the fixes for the same.
Regards,
Vignesh
Attachments:
v17-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v17-0001-Added-schema-level-support-for-publication.patchDownload
From 7802eb64bbc6aa3842ef44207281d81628e4b820 Mon Sep 17 00:00:00 2001
From: Vigneshwaran c <vignesh21@gmail.com>
Date: Mon, 26 Jul 2021 09:25:22 +0530
Subject: [PATCH v17 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 148 ++++++++++
src/backend/catalog/pg_publication.c | 228 ++++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 294 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 22 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 168 ++++++++++-
src/bin/pg_dump/pg_dump.h | 17 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 ++++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 41 +--
src/include/catalog/pg_publication_schema.h | 47 ++++
src/include/commands/publicationcmds.h | 22 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +
33 files changed, 1380 insertions(+), 168 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..35d7d4fcff 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* Fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3958,46 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4572,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5811,54 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..26a907bd17 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,92 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schemas cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schemas cannot be added to publications.")));
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +393,83 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets list of publication oids for publications marked as FOR SCHEMA.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationSchemaPsnspcidPspubidIndexId, true,
+ NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +508,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +554,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +580,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +630,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +730,19 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..35f47d3253 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..a38895b571 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +143,47 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -211,6 +257,15 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -224,6 +279,20 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -250,6 +319,35 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -310,19 +408,25 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubtype == PUBTYPE_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
@@ -362,19 +466,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -421,16 +537,85 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PublicationAddTables(pubid, rels, true, stmt);
CloseTableList(delrels);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
}
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ ListCell *oldlc;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaoidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +644,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +684,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -607,7 +818,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->schemas));
foreach(lc, rels)
{
@@ -631,6 +842,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->tables));
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +909,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -696,7 +974,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fcd778c62a..85deb9b5ab 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12265,6 +12266,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 39a2849eba..086eb38647 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,45 +9593,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9641,6 +9666,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9675,6 +9705,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16621,6 +16675,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index e4314af13a..283f9d2224 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1059,6 +1061,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1163,12 +1168,27 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ Oid psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaId),
+ ObjectIdGetDatum(pub->oid));
+
+ if (OidIsValid(psid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..2ec805eefe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5447,6 +5448,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5480,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..3e1f3cda09 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..2141302bad 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"
@@ -1630,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3950,6 +3955,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3964,25 +3970,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4000,6 +4018,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4024,6 +4043,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4066,7 +4086,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4133,6 +4153,100 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ /* Get the publication membership for the schema */
+ printfPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and
+ * process the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nsinfo->dobj.name;
+ pubsinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubsinfo[j].pubschema = nsinfo;
+ pubsinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4220,6 +4334,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubsinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubsinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10445,6 +10597,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18693,6 +18848,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..37554cee63 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -616,6 +618,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -629,6 +632,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8333558bda..5e2b7d43cb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 15 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 064892bade..49728abc6a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2641,15 +2650,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..b55d5205b1 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,26 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..76c10f2b3c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,35 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "parser/parse_node.h"
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f7b009ec43..4653f02624 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e28248af32..dd1396d793 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* type of this schemaspec */
+ char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3632,6 +3650,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3646,6 +3665,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..4415d9cd76 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..736df15463 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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -87,10 +87,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -102,19 +102,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -133,10 +133,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -149,10 +149,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -172,10 +172,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -213,10 +213,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -260,10 +260,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -273,20 +273,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..3b4f62025f 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -141,6 +141,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..fb5daa49eb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,6 +2047,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2332,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v17-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v17-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From 93287949999351ea760c8ffac77c6cc4051ebb75 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 2 Aug 2021 16:45:34 +0530
Subject: [PATCH v17 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 358 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 159 +++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++++-
9 files changed, 871 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..93a2d8a364 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6169,6 +6174,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-alterpublication">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6263,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11364,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..c05029b9a6 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..39628e4fa6 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a4ee54d516..fcdd277b83 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2301,6 +2301,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2337,6 +2346,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 736df15463..d8d7006f24 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,48 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +136,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -257,7 +299,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +330,324 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schemas cannot be added to publications.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+-- alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | e
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..33dbdf7bed 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,39 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -148,7 +175,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +195,140 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 0c84d87873..a0e116c97d 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = PostgresNode->new('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.30.2
On Monday, August 2, 2021 11:40 PM vignesh C <vignesh21@gmail.com>wrote:
Thanks for the comments, attached v17 patches has the fixes for the same.
Thanks for your new patch.
I saw the following warning when compiling. It seems we don't need this variable any more.
publicationcmds.c: In function ‘AlterPublicationSchemas’:
publicationcmds.c:592:15: warning: unused variable ‘oldlc’ [-Wunused-variable]
ListCell *oldlc;
^~~~~
Regards
Tang
On August 2, 2021 11:40 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, attached v17 patches has the fixes for the same.
Thanks for the new patch, it looks good to me except one minor thing:
It might be better to add the [CREATE PUBLICATION xxx FOR SCHEMA ] in tab-complete.c
Best regards,
houzj
On Tuesday, August 3, 2021 4:10 PM houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com> wrote:
On August 2, 2021 11:40 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, attached v17 patches has the fixes for the same.
Thanks for the new patch, it looks good to me except one minor thing:
It might be better to add the [CREATE PUBLICATION xxx FOR SCHEMA ] in
tab-complete.c
Sorry, the patch already had the logic, please ignore this comment.
Best regards,
houzj
On Tue, Aug 3, 2021 at 12:00 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Monday, August 2, 2021 11:40 PM vignesh C <vignesh21@gmail.com>wrote:
Thanks for the comments, attached v17 patches has the fixes for the same.
Thanks for your new patch.
I saw the following warning when compiling. It seems we don't need this variable any more.
publicationcmds.c: In function ‘AlterPublicationSchemas’:
publicationcmds.c:592:15: warning: unused variable ‘oldlc’ [-Wunused-variable]
ListCell *oldlc;
^~~~~
Thanks for reporting this, this is fixed in the v18 patch attached.
Regards,
Vignesh
Attachments:
v18-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v18-0001-Added-schema-level-support-for-publication.patchDownload
From 09aa5d7de7a3b0e14e9c4ccb0711d145a7b5411a Mon Sep 17 00:00:00 2001
From: Vigneshwaran c <vignesh21@gmail.com>
Date: Mon, 26 Jul 2021 09:25:22 +0530
Subject: [PATCH v18 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" type (if true) or the "FOR TABLE" type (if false). With the
introduction of the "FOR SCHEMA" publication type, it is not easy to determine
the publication type. Therefore, a new column "pubtype" has been added to the
pg_publication relation to indicate the publication type.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table type publication and then checking
pg_publication_schema for schema type publication. Instead, I preferred to add
the "pubtype" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubtype updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 148 ++++++++++
src/backend/catalog/pg_publication.c | 228 ++++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 293 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/parser/gram.y | 120 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 22 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 168 ++++++++++-
src/bin/pg_dump/pg_dump.h | 17 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 ++++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 41 +--
src/include/catalog/pg_publication_schema.h | 47 ++++
src/include/commands/publicationcmds.h | 22 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 ++
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++----
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +
33 files changed, 1379 insertions(+), 168 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..35d7d4fcff 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* Fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -3902,6 +3958,46 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4572,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5811,54 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ char *nspname;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
+
+ appendStringInfo(&buffer, "%s in publication %s", nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..26a907bd17 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,92 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schemas cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schemas cannot be added to publications.")));
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +393,83 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets list of publication oids for publications marked as FOR SCHEMA.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationSchemaPsnspcidPspubidIndexId, true,
+ NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +508,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +554,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +580,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +630,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubtype = pubform->pubtype;
ReleaseSysCache(tup);
@@ -530,13 +730,19 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubtype == PUBTYPE_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubtype == PUBTYPE_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubtype == PUBTYPE_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..35f47d3253 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..5fbbf3e55d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -53,6 +55,9 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +143,47 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -211,6 +257,15 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubtype - 1] = PUBTYPE_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -224,6 +279,20 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+ Relation nspcrel;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -250,6 +319,35 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication type in pg_publication relation.
+ */
+static void
+UpdatePublicationTypeTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubtype)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubtype;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -310,19 +408,25 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubtype == PUBTYPE_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
@@ -362,19 +466,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubtype == PUBTYPE_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
if (stmt->tableAction == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup, Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
+ }
else if (stmt->tableAction == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
@@ -421,16 +537,84 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PublicationAddTables(pubid, rels, true, stmt);
CloseTableList(delrels);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
}
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->pubtype == PUBTYPE_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubtype == PUBTYPE_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ if (stmt->tableAction == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+ else if (stmt->tableAction == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaoidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_SCHEMA);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +643,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +683,30 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -607,7 +817,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->schemas));
foreach(lc, rels)
{
@@ -631,6 +841,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->tables));
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +908,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -696,7 +973,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubtype == PUBTYPE_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fcd778c62a..85deb9b5ab 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12265,6 +12266,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index bcf85e8980..a2ae471eff 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,45 +9593,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9641,6 +9666,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9675,6 +9705,30 @@ AlterPublicationStmt:
n->tableAction = DEFELEM_DROP;
$$ = (Node *)n;
}
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->tableAction = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -16630,6 +16684,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index e4314af13a..283f9d2224 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1059,6 +1061,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1163,12 +1168,27 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ Oid psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaId),
+ ObjectIdGetDatum(pub->oid));
+
+ if (OidIsValid(psid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..2ec805eefe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5447,6 +5448,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5480,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..3e1f3cda09 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication schemas");
+ getPublicationSchemas(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..2141302bad 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"
@@ -1630,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3950,6 +3955,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubtype;
int i,
ntups;
@@ -3964,25 +3970,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubtype "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubtype FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubtype "
"FROM pg_publication p",
username_subquery);
@@ -4000,6 +4018,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubtype = PQfnumber(res, "pubtype");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4024,6 +4043,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubtype = get_publication_type(PQgetvalue(res, i, i_pubtype));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4066,7 +4086,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubtype == PUBTYPE_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4133,6 +4153,100 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationSchemas
+ * get information about publication membership for dumpable schemas
+ */
+void
+getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[], int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_schemaoid;
+ int i_oid;
+ int i_pubname;
+ int i_pubid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numSchemas; i++)
+ {
+ NamespaceInfo *nsinfo = &nspinfo[i];
+ PublicationInfo *pubinfo;
+
+ /*
+ * Ignore publication membership of schemas whose definitions are not
+ * to be dumped.
+ */
+ if (!(nsinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ pg_log_info("reading publication membership for schema \"%s\"",
+ nsinfo->dobj.name);
+
+ /* Get the publication membership for the schema */
+ printfPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ if (ntups == 0)
+ {
+ /*
+ * Schema is not a member of any publications. Clean up and
+ * process the next schema.
+ */
+ PQclear(res);
+ continue;
+ }
+
+ i_schemaoid = PQfnumber(res, "psnspcid");
+ i_oid = PQfnumber(res, "oid");
+ i_pubname = PQfnumber(res, "pubname");
+ i_pubid = PQfnumber(res, "pubid");
+
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, j, i_pubid));
+
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_schemaoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nsinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nsinfo->dobj.name;
+ pubsinfo[j].pubname = pg_strdup(PQgetvalue(res, j, i_pubname));
+ pubsinfo[j].pubschema = nsinfo;
+ pubsinfo[j].publication = pubinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4220,6 +4334,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubsinfo->pubname, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubsinfo->pubname));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10445,6 +10597,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18693,6 +18848,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..37554cee63 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -616,6 +618,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubtype;
} PublicationInfo;
/*
@@ -629,6 +632,18 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ char *pubname;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationSchemas(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8333558bda..5e2b7d43cb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubtype = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubtype = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubtype = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubtype AS \"%s\"",
+ gettext_noop("PubType"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubtype;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubtype = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubtype)
+ appendPQExpBufferStr(&buf,
+ ", pubtype");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubtype = PUBTYPE_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubtype)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubtype)
+ printTableAddHeader(&cont, gettext_noop("Pubtype"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubtype)
+ {
+ char *type = PQgetvalue(res, i, 9);
+
+ pubtype = get_publication_type(type);
+ printTableAddCell(&cont, type, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 15 check was based on all tables */
+ if ((has_pubtype && pubtype == PUBTYPE_TABLE) ||
+ (!has_pubtype && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubtype && pubtype == PUBTYPE_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 064892bade..49728abc6a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2641,15 +2650,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..b55d5205b1 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBTYPE_xxx constants below */
+ char pubtype;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubtype;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,26 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication types */
+#define PUBTYPE_ALLTABLES 'a' /* all tables publication */
+#define PUBTYPE_TABLE 't' /* table publication */
+#define PUBTYPE_SCHEMA 's' /* schema publication */
+#define PUBTYPE_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication type.
+*/
+static inline char
+get_publication_type(char *strpubtype)
+{
+ if (strcmp(strpubtype, "a") == 0)
+ return PUBTYPE_ALLTABLES;
+ else if (strcmp(strpubtype, "t") == 0)
+ return PUBTYPE_TABLE;
+ else if (strcmp(strpubtype, "s") == 0)
+ return PUBTYPE_SCHEMA;
+
+ return PUBTYPE_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..76c10f2b3c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,35 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "parser/parse_node.h"
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f7b009ec43..4653f02624 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index af5c16abfa..2e1de8543a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* type of this schemaspec */
+ char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3632,6 +3650,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
typedef struct AlterPublicationStmt
@@ -3646,6 +3665,7 @@ typedef struct AlterPublicationStmt
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..4415d9cd76 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..736df15463 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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubType
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -87,10 +87,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -102,19 +102,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -133,10 +133,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -149,10 +149,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -172,10 +172,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -213,10 +213,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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -260,10 +260,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -273,20 +273,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 | Truncates | Via root | PubType
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubType
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..3b4f62025f 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -141,6 +141,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..fb5daa49eb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,6 +2047,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2332,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v18-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v18-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From 371627904bc366b4d4ecf1c34db9e4b43dd891fe Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 2 Aug 2021 16:45:34 +0530
Subject: [PATCH v18 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 358 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 159 +++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++++-
9 files changed, 871 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..93a2d8a364 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6169,6 +6174,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubtype</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication type:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication type,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication type,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication type,
+ <literal>e</literal> = Empty publication type.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication type. When a table or schema is added to
+ the publication using <link linkend="sql-alterpublication">
+ <command>ALTER PUBLICATION</command></link> then the publication type
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication type cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6263,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11364,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..c05029b9a6 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..39628e4fa6 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a4ee54d516..fcdd277b83 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2301,6 +2301,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2337,6 +2346,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 736df15463..d8d7006f24 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,48 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +136,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -257,7 +299,6 @@ DROP PUBLICATION testpub2;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +330,324 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schemas cannot be added to publications.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+-- alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | e
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubtype
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..33dbdf7bed 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,39 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -148,7 +175,6 @@ SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +195,140 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 0c84d87873..a0e116c97d 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = PostgresNode->new('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.30.2
On Tue, Aug 3, 2021 at 8:38 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this, this is fixed in the v18 patch attached.
I have started looking into this patch and below are some initial comments.
1.
+ /* Fetch publication name and schema oid from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
I think the comment should be: "Fetch schema name and publication name
from input list"
2.
@@ -3902,6 +3958,46 @@ getObjectDescription(const ObjectAddress
*object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ HeapTuple tup;
+ char *pubname;
+ Form_pg_publication_schema psform;
+ char *nspname;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ break;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ pubname = get_publication_name(psform->pspubid, false);
+ nspname = get_namespace_name(psform->psnspcid);
+ if (!nspname)
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ break;
+ }
The above code in getObjectDescription looks quite similar to what you
have in getObjectIdentityParts(). Can we extract the common code into
a separate function?
3. Can we use column name pubkind (similar to relkind in pg_class)
instead of pubtype? If so, please change PUBTYPE_ALLTABLES and similar
other defines to PUBKIND_*.
4.
@@ -3632,6 +3650,7 @@ typedef struct CreatePublicationStmt
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
bool for_all_tables; /* Special publication for all tables in db */
+ List *schemas; /* Optional list of schemas */
} CreatePublicationStmt;
Isn't it better to keep a schemas list after tables?
5.
@@ -1163,12 +1168,27 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubtype == PUBTYPE_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubtype == PUBTYPE_SCHEMA)
+ {
+ Oid schemaId = get_rel_namespace(relid);
+ Oid psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaId),
+ ObjectIdGetDatum(pub->oid));
+
+ if (OidIsValid(psid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
Isn't it better to get schema publications once as we get relation
publications via GetRelationPublications and then decide whether to
publish or not? I think that will save repeated cache searches for
each publication requested by the subscriber?
6.
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
Why don't we keep pubid as the first column in this index?
7.
getPublicationSchemas()
{
..
+ /* Get the publication membership for the schema */
+ printfPQExpBuffer(query,
+ "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid "
+ "FROM pg_publication_schema ps, pg_publication p "
+ "WHERE ps.psnspcid = '%u' "
+ "AND p.oid = ps.pspubid",
+ nsinfo->dobj.catId.oid);
..
}
Why do you need to use join here? Why the query and another handling
in this function be similar to what we have in getPublicationTables?
Also, there is another function GetPublicationSchemas() in the patch,
can we name one of these differently for the purpose of easy
grepability?
--
With Regards,
Amit Kapila.
On Tuesday, August 3, 2021 11:08 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this, this is fixed in the v18 patch attached.
Thanks for fixing it.
Few suggestions for V18:
1.
+# Clean up the tables on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
Should we change the comment to "Clean up the schemas ... ", instead of 'tables'?
2.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
Spaces are used here(and some other places), but in most places we use a TAB, so
I think it's better to change it to a TAB.
3.
List *tables; /* List of tables to add/drop */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction tableAction; /* What action to perform with the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;
Should we change the comment here to 'List of schemas to add/drop', then it can
be consistent with the comment for 'tables'.
I also noticed that 'tableAction' variable is used when we add/drop/set schemas,
so maybe the variable name is not suitable any more.
Besides, the following comment is above these codes. Should we add some comments
for schema?
/* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
And it says 'add/drop', do we need to add 'set'? (it's not related to this
patch, so I think I can add it in another thread[1]/messages/by-id/OS0PR01MB6113480F937572BF1216DD61FBEF9@OS0PR01MB6113.jpnprd01.prod.outlook.com if needed, which is related
to comment improvement)
4.
I saw the existing tests about permissions in publication.sql, should we add
tests for schema publication? Like this:
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 33dbdf7bed..c19337631e 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -160,16 +160,19 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
+ALTER PUBLICATION testpub3 ADD SCHEMA pub_test; -- ok
-DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub2, testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
[1]: /messages/by-id/OS0PR01MB6113480F937572BF1216DD61FBEF9@OS0PR01MB6113.jpnprd01.prod.outlook.com
Regards
Tang
On Wed, Aug 4, 2021 at 4:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Aug 3, 2021 at 8:38 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this, this is fixed in the v18 patch attached.
I have started looking into this patch and below are some initial comments.
Few more comments:
===================
1. Do we need the previous column 'puballtables' after adding pubtype
or pubkind in pg_publication?
2.
@@ -224,6 +279,20 @@ CreatePublication(ParseState *pstate,
CreatePublicationStmt *stmt)
..
+ nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ table_close(nspcrel, ShareUpdateExclusiveLock);
What is the reason for opening and taking a lock on
NamespaceRelationId? Do you want to avoid dropping the corresponding
schema during this duration? If so, that is not sufficient because
what if somebody drops it just before you took lock on
NamespaceRelationId. I think you need to use LockDatabaseObject to
avoid dropping the schema and note that it should be unlocked only at
the end of the transaction similar to what we do for tables. I guess
you need to add this locking inside the function
PublicationAddSchemas. Also, it would be good if you can add few
comments in this part of the code to explain the reason for locking.
3. The function PublicationAddSchemas is called from multiple places
in the patch but the locking protection is not there at all places. I
think if you add locking as suggested in the previous point then it
should be okay. I think you need similar locking for
PublicationDropSchemas.
4.
@@ -421,16 +537,84 @@ AlterPublicationTables(AlterPublicationStmt
*stmt, Relation rel,
PublicationAddTables(pubid, rels, true, stmt);
CloseTableList(delrels);
+ if (pubform->pubtype == PUBTYPE_EMPTY)
+ UpdatePublicationTypeTupleValue(rel, tup,
+ Anum_pg_publication_pubtype,
+ PUBTYPE_TABLE);
}
At the above and all other similar places, the patch first updates the
pg_publication after performing the rel/schema operation. Isn't it
better to first update pg_publication to remain in sync with how
CreatePublication works? I am not able to see any issue with the way
you have it in the patch but it is better to keep the code consistent
across various similar functions to avoid confusion in the future.
--
With Regards,
Amit Kapila.
On Wed, Aug 4, 2021 at 12:08 AM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Aug 3, 2021 at 12:00 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:On Monday, August 2, 2021 11:40 PM vignesh C <vignesh21@gmail.com>wrote:
Thanks for the comments, attached v17 patches has the fixes for the same.
Thanks for your new patch.
I saw the following warning when compiling. It seems we don't need this variable any more.
publicationcmds.c: In function ‘AlterPublicationSchemas’:
publicationcmds.c:592:15: warning: unused variable ‘oldlc’ [-Wunused-variable]
ListCell *oldlc;
^~~~~Thanks for reporting this, this is fixed in the v18 patch attached.
I've also started reviewing this patch. I've not looked at the patch
yet but here are initial comments/questions based on using this
feature:
pg_publication catalog still has puballtables column but it's still
necessary? IIUC since pubtype = 'a' means publishing all tables in the
database puballtables seems no longer necessary.
---
Suppose that a parent table and its child table are defined in
different schemas, there is a publication for the schema where only
the parent table is defined, and the subscriber subscribes to the
publication, should changes for its child table be replicated to the
subscriber?
In FOR TABLE cases, i.g., where the subscriber subscribes to the
publication that is only for the parent table, changes for its child
table are replicated to the subscriber.
As far as I tested v18 patch, changes for the child table are not
replicated in FOR SCHEMA cases. Here is the test script:
On publisher and subscriber:
create schema p_schema;
create schema c_schema;
create table p_schema.p (a int) partition by list (a);
create table c_schema.c partition of p_schema.p for values in (1);
On publisher:
create publication pub_p_schema for schema p_schema;
On subscriber:
create subscription pub connection 'dbname=postgres' publication pub_p_schema;
On publisher:
insert into p_schema.p values (1);
select * from p_schema.p;
a
---
1
(1 row)
On subscriber:
select * from p_schema.p;
a
---
(0 rows)
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Wed, Aug 4, 2021 at 4:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Aug 3, 2021 at 8:38 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this, this is fixed in the v18 patch attached.
I have started looking into this patch and below are some initial comments.
1. + /* Fetch publication name and schema oid from input list */ + schemaname = strVal(linitial(object)); + pubname = strVal(lsecond(object));I think the comment should be: "Fetch schema name and publication name
from input list"
Modified.
2.
@@ -3902,6 +3958,46 @@ getObjectDescription(const ObjectAddress
*object, bool missing_ok)
break;
}+ case OCLASS_PUBLICATION_SCHEMA: + { + HeapTuple tup; + char *pubname; + Form_pg_publication_schema psform; + char *nspname; + + tup = SearchSysCache1(PUBLICATIONSCHEMA, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + { + if (!missing_ok) + elog(ERROR, "cache lookup failed for publication schema %u", + object->objectId); + break; + } + + psform = (Form_pg_publication_schema) GETSTRUCT(tup); + pubname = get_publication_name(psform->pspubid, false); + nspname = get_namespace_name(psform->psnspcid); + if (!nspname) + { + Oid psnspcid = psform->psnspcid; + + pfree(pubname); + ReleaseSysCache(tup); + if (!missing_ok) + elog(ERROR, "cache lookup failed for schema %u", + psnspcid); + break; + }The above code in getObjectDescription looks quite similar to what you
have in getObjectIdentityParts(). Can we extract the common code into
a separate function?
Modified.
3. Can we use column name pubkind (similar to relkind in pg_class)
instead of pubtype? If so, please change PUBTYPE_ALLTABLES and similar
other defines to PUBKIND_*.
Modified.
4. @@ -3632,6 +3650,7 @@ typedef struct CreatePublicationStmt List *options; /* List of DefElem nodes */ List *tables; /* Optional list of tables to add */ bool for_all_tables; /* Special publication for all tables in db */ + List *schemas; /* Optional list of schemas */ } CreatePublicationStmt;Isn't it better to keep a schemas list after tables?
Modified.
5.
@@ -1163,12 +1168,27 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;- if (pub->alltables) + if (pub->pubtype == PUBTYPE_ALLTABLES) { publish = true; if (pub->pubviaroot && am_partition) publish_as_relid = llast_oid(get_partition_ancestors(relid)); } + else if (pub->pubtype == PUBTYPE_SCHEMA) + { + Oid schemaId = get_rel_namespace(relid); + Oid psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP, + Anum_pg_publication_schema_oid, + ObjectIdGetDatum(schemaId), + ObjectIdGetDatum(pub->oid)); + + if (OidIsValid(psid)) + { + publish = true; + if (pub->pubviaroot && am_partition) + publish_as_relid = llast_oid(get_partition_ancestors(relid)); + } + }Isn't it better to get schema publications once as we get relation
publications via GetRelationPublications and then decide whether to
publish or not? I think that will save repeated cache searches for
each publication requested by the subscriber?
Modified.
6. + {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */ + PublicationSchemaPsnspcidPspubidIndexId, + 2, + { + Anum_pg_publication_schema_psnspcid, + Anum_pg_publication_schema_pspubid, + 0, + 0 + },Why don't we keep pubid as the first column in this index?
I wanted to keep it similar to PUBLICATIONRELMAP, should we keep it as
it is, thoughts?
7. getPublicationSchemas() { .. + /* Get the publication membership for the schema */ + printfPQExpBuffer(query, + "SELECT ps.psnspcid, ps.oid, p.pubname, p.oid AS pubid " + "FROM pg_publication_schema ps, pg_publication p " + "WHERE ps.psnspcid = '%u' " + "AND p.oid = ps.pspubid", + nsinfo->dobj.catId.oid); .. }Why do you need to use join here? Why the query and another handling
in this function be similar to what we have in getPublicationTables?
Also, there is another function GetPublicationSchemas() in the patch,
can we name one of these differently for the purpose of easy
grepability?
Modified it similar to getPublicationTables without joins. The
function is renamed to getPublicationNamespaces.
Thanks for the comments, the attached v19 patch has the fixes for the comments.
Regards,
Vignesh
Attachments:
v19-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v19-0001-Added-schema-level-support-for-publication.patchDownload
From 5be8e633ad5b597cbc2b834fdf6da05a9f65b58a Mon Sep 17 00:00:00 2001
From: Vigneshwaran c <vignesh21@gmail.com>
Date: Mon, 26 Jul 2021 09:25:22 +0530
Subject: [PATCH v19 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" kind (if true) or the "FOR TABLE" kind (if false). With the
introduction of the "FOR SCHEMA" publication kind, it is not easy to determine
the publication kind. Therefore, a new column "pubkind" has been added to the
pg_publication relation to indicate the publication kind.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table kind publication and then checking
pg_publication_schema for schema kind publication. Instead, I preferred to add
the "pubkind" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubkind updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 143 +++++++++
src/backend/catalog/pg_publication.c | 228 +++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 330 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 2 +
src/backend/nodes/copyfuncs.c | 2 +-
src/backend/nodes/equalfuncs.c | 2 +-
src/backend/parser/gram.y | 126 ++++++--
src/backend/replication/pgoutput/pgoutput.c | 18 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 162 +++++++++-
src/bin/pg_dump/pg_dump.h | 16 +
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 222 ++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 41 ++-
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 22 ++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 24 +-
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 100 +++---
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +
35 files changed, 1405 insertions(+), 177 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..b3f0c27739 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,9 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
+
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1127,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1948,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2847,6 +2903,49 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ *pubname = get_publication_name(psform->pspubid, false);
+ *nspname = get_namespace_name(psform->psnspcid);
+ if (!(*nspname))
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3902,6 +4001,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4591,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5830,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..8f5dbe1997 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,9 +28,12 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
@@ -214,6 +217,92 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schemas cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schemas cannot be added to publications.")));
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -304,6 +393,83 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used for normal publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets list of publication oids for publications marked as FOR SCHEMA.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_psnspcid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(schemaid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationSchemaPsnspcidPspubidIndexId, true,
+ NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ result = lappend_oid(result, pubsch->pspubid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -342,29 +508,37 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
- * root partitioned tables.
+ * root partitioned tables. If schemaOid is specified, get the relations present
+ * in the schema specified.
*/
List *
-GetAllTablesPublicationRelations(bool pubviaroot)
+GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid)
{
Relation classRel;
- ScanKeyData key[1];
+ ScanKeyData key[2];
TableScanDesc scan;
HeapTuple tuple;
List *result = NIL;
+ int keycount = 0;
classRel = table_open(RelationRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
+ ScanKeyInit(&key[keycount++],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_RELATION));
- scan = table_beginscan_catalog(classRel, 1, key);
+ if (schemaOid != InvalidOid)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -380,12 +554,14 @@ GetAllTablesPublicationRelations(bool pubviaroot)
if (pubviaroot)
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -404,6 +580,29 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetAllTablesPublicationRelations(pubviaroot,
+ schemaOid);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -431,6 +630,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubkind = pubform->pubkind;
ReleaseSysCache(tup);
@@ -530,13 +730,19 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
- tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ if (publication->pubkind == PUBKIND_ALLTABLES)
+ tables = GetAllTablesPublicationRelations(publication->pubviaroot,
+ InvalidOid);
+ else if (publication->pubkind == PUBKIND_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubkind == PUBKIND_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->pubviaroot,
+ publication->oid);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..35f47d3253 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..2251c3271b 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -34,6 +36,7 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -53,6 +56,10 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +145,47 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -211,6 +259,15 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubkind - 1] = PUBKIND_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubkind - 1] = PUBKIND_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubkind - 1] = PUBKIND_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubkind - 1] = PUBKIND_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -224,6 +281,24 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automaically at the end of create publication
+ * command.
+ */
+ LockSchemaList(schemaoidlist);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -250,6 +325,35 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication kind in pg_publication relation.
+ */
+static void
+UpdatePublicationKindTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubkind)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[col - 1] = pubkind;
+ replaces[col - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -310,19 +414,25 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubkind == PUBKIND_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ if (pubform->pubkind == PUBKIND_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubkind == PUBKIND_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->pubviaroot,
+ pubform->oid);
/*
* We don't want to send too many individual messages, at some point
@@ -362,20 +472,32 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubkind == PUBKIND_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (pubform->pubkind == PUBKIND_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR SCHEMA publications.")));
+
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
- if (stmt->tableAction == DEFELEM_ADD)
+ if (stmt->action == DEFELEM_ADD)
+ {
+ if (pubform->pubkind == PUBKIND_EMPTY)
+ UpdatePublicationKindTupleValue(rel, tup, Anum_pg_publication_pubkind,
+ PUBKIND_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -384,6 +506,11 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ if (pubform->pubkind == PUBKIND_EMPTY)
+ UpdatePublicationKindTupleValue(rel, tup,
+ Anum_pg_publication_pubkind,
+ PUBKIND_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -426,11 +553,82 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, Form_pg_publication pubform)
+{
+ List *schemaoidlist = NIL;
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->pubkind == PUBKIND_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (pubform->pubkind == PUBKIND_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to or dropped from FOR TABLE publications.")));
+
+ /* Convert the text list into oid list */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks will
+ * be released automaically at the end of alter publication command.
+ */
+ LockSchemaList(schemaoidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ if (pubform->pubkind == PUBKIND_EMPTY)
+ UpdatePublicationKindTupleValue(rel, tup,
+ Anum_pg_publication_pubkind,
+ PUBKIND_SCHEMA);
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ if (pubform->pubkind == PUBKIND_EMPTY)
+ UpdatePublicationKindTupleValue(rel, tup,
+ Anum_pg_publication_pubkind,
+ PUBKIND_SCHEMA);
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaoidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +657,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup, pubform);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +697,49 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in
+ * ShareUpdateExclusiveLock mode in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+ foreach(lc, schemalist)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaoid, 0, AccessShareLock);
+ }
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -607,7 +850,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->schemas));
foreach(lc, rels)
{
@@ -631,6 +874,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->tables));
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +941,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -696,7 +1006,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubkind == PUBKIND_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fcd778c62a..85deb9b5ab 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12265,6 +12266,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 29020c908e..973b938f91 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4835,7 +4835,7 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(tables);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..f4f7e896dd 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2309,7 +2309,7 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(tables);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 39a2849eba..6fac2a4c30 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,45 +9593,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9641,6 +9666,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9656,7 +9686,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
| ALTER PUBLICATION name SET TABLE relation_expr_list
@@ -9664,7 +9694,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
| ALTER PUBLICATION name DROP TABLE relation_expr_list
@@ -9672,7 +9702,31 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->action = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->action = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->action = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -16621,6 +16675,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 286119c8c8..6f3e5323ab 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1068,6 +1070,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1151,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1172,12 +1179,21 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubkind == PUBKIND_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubkind == PUBKIND_SCHEMA)
+ {
+ if (list_member_oid(schemaPubids, pub->oid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..2ec805eefe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5447,6 +5448,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5480,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..3010485f47 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..9a17adbec9 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"
@@ -1630,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3950,6 +3955,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubkind;
int i,
ntups;
@@ -3964,25 +3970,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubkind "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubkind FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubkind FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubkind "
"FROM pg_publication p",
username_subquery);
@@ -4000,6 +4018,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubkind = PQfnumber(res, "pubkind");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4024,6 +4043,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubkind = get_publication_kind(PQgetvalue(res, i, i_pubkind));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4066,7 +4086,7 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if (pubinfo->puballtables || pubinfo->pubkind == PUBKIND_ALLTABLES)
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4133,6 +4153,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pspubid;
+ int i_psnspcid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pspubid, psnspcid "
+ "FROM pg_catalog.pg_publication_schema");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pspubid = PQfnumber(res, "pspubid");
+ i_psnspcid = PQfnumber(res, "psnspcid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, i, i_pspubid));
+ Oid psnspcid = atooid(PQgetvalue(res, i, i_psnspcid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(psnspcid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4220,6 +4328,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10445,6 +10591,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18693,6 +18842,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..daad2802cd 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -616,6 +618,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubkind;
} PublicationInfo;
/*
@@ -629,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8333558bda..af6cb8549c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubkind = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubkind = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubkind = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6147,7 +6230,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};
if (pset.sversion < 100000)
{
@@ -6182,6 +6265,10 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n pubkind AS \"%s\"",
+ gettext_noop("PubKind"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubkind;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,6 +6363,7 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubkind = (pset.sversion >= 150000);
initPQExpBuffer(&buf);
@@ -6250,6 +6377,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubkind)
+ appendPQExpBufferStr(&buf,
+ ", pubkind");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6287,20 +6418,18 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ char pubkind = PUBKIND_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubkind)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6315,6 +6444,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubkind)
+ printTableAddHeader(&cont, gettext_noop("Pubkind"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
@@ -6325,8 +6456,17 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubkind)
+ {
+ char *kind = PQgetvalue(res, i, 9);
+
+ pubkind = get_publication_kind(kind);
+ printTableAddCell(&cont, kind, false, false);
+ }
- if (!puballtables)
+ /* Prior to version 15 check was based on all tables */
+ if ((has_pubkind && pubkind == PUBKIND_TABLE) ||
+ (!has_pubkind && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6477,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubkind && pubkind == PUBKIND_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6503,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 064892bade..49728abc6a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2641,15 +2650,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..d6a2a95e29 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -54,6 +53,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBKIND_xxx constants below */
+ char pubkind;
} FormData_pg_publication;
/* ----------------
@@ -81,12 +83,9 @@ typedef struct Publication
bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubkind;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +102,26 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication kinds */
+#define PUBKIND_ALLTABLES 'a' /* all tables publication */
+#define PUBKIND_TABLE 't' /* table publication */
+#define PUBKIND_SCHEMA 's' /* schema publication */
+#define PUBKIND_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication kind.
+*/
+static inline char
+get_publication_kind(char *strpubkind)
+{
+ if (strcmp(strpubkind, "a") == 0)
+ return PUBKIND_ALLTABLES;
+ else if (strcmp(strpubkind, "t") == 0)
+ return PUBKIND_TABLE;
+ else if (strcmp(strpubkind, "s") == 0)
+ return PUBKIND_SCHEMA;
+
+ return PUBKIND_EMPTY;
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..76c10f2b3c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,35 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "parser/parse_node.h"
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot, Oid schemaOid);
+extern List *GetAllSchemasPublicationRelations(bool pubviaroot, Oid puboid);
+
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f7b009ec43..4653f02624 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,6 +484,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e28248af32..d96aafd626 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* type of this schemaspec */
+ char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
+ List *schemas; /* Optional list of schemas to add */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3642,10 +3661,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
+ /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE/SCHEMA */
List *tables; /* List of tables to add/drop */
+ List *schemas; /* List of schemas to add/drop/set */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..4415d9cd76 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..ae42aba1bf 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 | Truncates | Via root | PubKind
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | f | t | f | f | f | e
(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 | Truncates | Via root | PubKind
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e
+ testpub_default | regress_publication_user | f | t | t | t | f | f | e
(2 rows)
--- adding tables
@@ -87,10 +87,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 | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | t | t | t | f | f | f | a
(1 row)
DROP TABLE testpub_tbl2;
@@ -102,19 +102,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 | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
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 | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_tbl3"
@@ -133,10 +133,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"public.testpub_parted"
@@ -149,10 +149,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 | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -172,10 +172,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 | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -213,10 +213,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 | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -260,10 +260,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | f | f | t
(1 row)
-- fail - must be owner of publication
@@ -273,20 +273,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 | Truncates | Via root | PubKind
+-------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | t
(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 | Truncates | Via root | PubKind
+-----------------+---------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..3b4f62025f 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -141,6 +141,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..fb5daa49eb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,6 +2047,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2332,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v19-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v19-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From 3207b25d652f118460fc8b86ba7e8dcf9ea833ec Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 2 Aug 2021 16:45:34 +0530
Subject: [PATCH v19 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 94 ++++-
doc/src/sgml/ref/alter_publication.sgml | 45 ++-
doc/src/sgml/ref/create_publication.sgml | 44 ++-
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 363 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 163 ++++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++++-
9 files changed, 880 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..f5a185a328 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6169,6 +6174,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubkind</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication kind:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication kind,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication kind,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication kind,
+ <literal>e</literal> = Empty publication kind.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication kind. When a table or schema is added to
+ the publication using <link linkend="sql-alterpublication">
+ <command>ALTER PUBLICATION</command></link> then the publication kind
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication kind cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6263,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11364,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..c05029b9a6 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -97,6 +111,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +164,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..39628e4fa6 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ clause requires the invoking user to be a superuser.
</para>
<para>
@@ -222,6 +234,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a4ee54d516..fcdd277b83 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2301,6 +2301,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2337,6 +2346,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index ae42aba1bf..8c99d31ef8 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,48 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to or dropped from FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added to or dropped from FOR TABLE publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added to or dropped from FOR SCHEMA publications.
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +136,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -246,18 +288,22 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD SCHEMA pub_test; -- fail
+ERROR: must be owner of schema pub_test
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
+ALTER PUBLICATION testpub3 ADD SCHEMA pub_test; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +335,324 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f | t
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schemas cannot be added to publications.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+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 | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD 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 | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP 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 | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET 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 | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "public"
+
+-- alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | e
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | Pubkind
+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ regress_publication_user | f | t | t | t | t | f | s
+Schemas:
+ "pub_test1"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..d67d387559 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,39 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -133,22 +160,25 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
+ALTER PUBLICATION testpub3 ADD SCHEMA pub_test; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +199,140 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema should change the publication type from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 0c84d87873..556af52275 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = PostgresNode->new('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.30.2
On Wed, Aug 4, 2021 at 8:08 PM tanghy.fnst@fujitsu.com <
tanghy.fnst@fujitsu.com> wrote:
On Tuesday, August 3, 2021 11:08 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this, this is fixed in the v18 patch attached.
Thanks for fixing it.
Few suggestions for V18:
1.
+# Clean up the tables on both publisher and subscriber as we don't need
them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade"); +$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade"); +$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade"); +$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade"); +$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade"); +$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");Should we change the comment to "Clean up the schemas ... ", instead of
'tables'?
Modified.
2. +$result = $node_subscriber->safe_psql('postgres', + "SELECT count(*) FROM sch1.tab3");Spaces are used here(and some other places), but in most places we use a
TAB, so
I think it's better to change it to a TAB.
Modified.
3.
List *tables; /* List of tables to
add/drop */
bool for_all_tables; /* Special publication for all
tables in db */
DefElemAction tableAction; /* What action to perform with
the tables */
+ List *schemas; /* Optional list of schemas */
} AlterPublicationStmt;Should we change the comment here to 'List of schemas to add/drop', then
it can
be consistent with the comment for 'tables'.
Modified.
I also noticed that 'tableAction' variable is used when we add/drop/set
schemas,
so maybe the variable name is not suitable any more.
Changed the variable name.
Besides, the following comment is above these codes. Should we add some
comments
for schema?
/* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
Modified
And it says 'add/drop', do we need to add 'set'? (it's not related to this
patch, so I think I can add it in another thread[1] if needed, which is
related
to comment improvement)
You can include the change in the patch posted.
4.
I saw the existing tests about permissions in publication.sql, should we
add
tests for schema publication? Like this:
diff --git a/src/test/regress/sql/publication.sql
b/src/test/regress/sql/publication.sql
index 33dbdf7bed..c19337631e 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -160,16 +160,19 @@ GRANT CREATE ON DATABASE regression TO
regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD SCHEMA pub_test; -- failSET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
+ALTER PUBLICATION testpub3 ADD SCHEMA pub_test; -- ok-DROP PUBLICATION testpub2; +DROP PUBLICATION testpub2, testpub3;SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
Added.
The changes for the above are available in the v19 patch posted at [1]/messages/by-id/CALDaNm3BMLBpWOSdS3Q2vwpsM=0yovpJm8dKHRqNyFpANbrhpw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm3BMLBpWOSdS3Q2vwpsM=0yovpJm8dKHRqNyFpANbrhpw@mail.gmail.com
/messages/by-id/CALDaNm3BMLBpWOSdS3Q2vwpsM=0yovpJm8dKHRqNyFpANbrhpw@mail.gmail.com
Regards,
Vignesh
On Thu, Aug 5, 2021 at 3:54 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Aug 4, 2021 at 4:10 PM Amit Kapila <amit.kapila16@gmail.com>
wrote:
On Tue, Aug 3, 2021 at 8:38 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for reporting this, this is fixed in the v18 patch attached.
I have started looking into this patch and below are some initial
comments.
Few more comments:
===================
1. Do we need the previous column 'puballtables' after adding pubtype
or pubkind in pg_publication?
I felt this should be retained as old client will still be using
puballtables, like in case of old client executing \dRp+ commands.
2. @@ -224,6 +279,20 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) .. + nspcrel = table_open(NamespaceRelationId, ShareUpdateExclusiveLock); + PublicationAddSchemas(puboid, schemaoidlist, true, NULL); + table_close(nspcrel, ShareUpdateExclusiveLock);What is the reason for opening and taking a lock on
NamespaceRelationId? Do you want to avoid dropping the corresponding
schema during this duration? If so, that is not sufficient because
what if somebody drops it just before you took lock on
NamespaceRelationId. I think you need to use LockDatabaseObject to
avoid dropping the schema and note that it should be unlocked only at
the end of the transaction similar to what we do for tables. I guess
you need to add this locking inside the function
PublicationAddSchemas. Also, it would be good if you can add few
comments in this part of the code to explain the reason for locking.
Modified.
3. The function PublicationAddSchemas is called from multiple places
in the patch but the locking protection is not there at all places. I
think if you add locking as suggested in the previous point then it
should be okay. I think you need similar locking for
PublicationDropSchemas.
Modified.
4.
@@ -421,16 +537,84 @@ AlterPublicationTables(AlterPublicationStmt
*stmt, Relation rel,
PublicationAddTables(pubid, rels, true, stmt);CloseTableList(delrels); + if (pubform->pubtype == PUBTYPE_EMPTY) + UpdatePublicationTypeTupleValue(rel, tup, + Anum_pg_publication_pubtype, + PUBTYPE_TABLE); }At the above and all other similar places, the patch first updates the
pg_publication after performing the rel/schema operation. Isn't it
better to first update pg_publication to remain in sync with how
CreatePublication works? I am not able to see any issue with the way
you have it in the patch but it is better to keep the code consistent
across various similar functions to avoid confusion in the future.
Modified.
Thanks for the comments, the changes for the above are available in the v19
patch posted at [1]/messages/by-id/CALDaNm3BMLBpWOSdS3Q2vwpsM=0yovpJm8dKHRqNyFpANbrhpw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm3BMLBpWOSdS3Q2vwpsM=0yovpJm8dKHRqNyFpANbrhpw@mail.gmail.com
/messages/by-id/CALDaNm3BMLBpWOSdS3Q2vwpsM=0yovpJm8dKHRqNyFpANbrhpw@mail.gmail.com
Regards,
Vignesh
On Fri, Aug 6, 2021 at 2:16 PM vignesh C <vignesh21@gmail.com> wrote:
On Thu, Aug 5, 2021 at 3:54 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
Few more comments:
===================
1. Do we need the previous column 'puballtables' after adding pubtype
or pubkind in pg_publication?I felt this should be retained as old client will still be using puballtables, like in case of old client executing \dRp+ commands.
But do we guarantee that old clients work with newer server versions?
For example, psql docs say: "psql works best with servers of the same
or an older major version. Backslash commands are particularly likely
to fail if the server is of a newer version than psql itself." [1]https://www.postgresql.org/docs/devel/app-psql.html
(See Notes Section). Similarly, pg_dump docs say: "However, pg_dump
cannot dump from PostgreSQL servers newer than its own major version;
it will refuse to even try, rather than risk making an invalid dump."
[2]: https://www.postgresql.org/docs/devel/app-pgdump.html
It seems Sawada-San has the same question and IIUC docs suggest we
don't need such compatibility, so what makes you think we need it?
[1]: https://www.postgresql.org/docs/devel/app-psql.html
[2]: https://www.postgresql.org/docs/devel/app-pgdump.html
--
With Regards,
Amit Kapila.
On Fri, Aug 6, 2021 at 2:02 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 4, 2021 at 4:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Aug 3, 2021 at 8:38 PM vignesh C <vignesh21@gmail.com> wrote:
6. + {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */ + PublicationSchemaPsnspcidPspubidIndexId, + 2, + { + Anum_pg_publication_schema_psnspcid, + Anum_pg_publication_schema_pspubid, + 0, + 0 + },Why don't we keep pubid as the first column in this index?
I wanted to keep it similar to PUBLICATIONRELMAP, should we keep it as
it is, thoughts?
Okay, I see your point. I think for PUBLICATIONRELMAP, we need it
because it is searched using the only relid in
GetRelationPublications, so, similarly, in the patch, you are using
schema_oid in GetSchemaPublications, so probably that will help. I was
wondering why you haven't directly used the cache in
GetSchemaPublications similar to GetRelationPublications? It seems
there is a danger for concurrent object drop. Can you please check how
the safety is ensured say when either one wants to drop the
corresponding relation/schema or publication? Another point is why
can't we use the other index (where the index is on relid or
schema_oid (PublicationSchemaObjectIndexId))?
--
With Regards,
Amit Kapila.
On Fri, Aug 6, 2021 at 4:02 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 6, 2021 at 2:16 PM vignesh C <vignesh21@gmail.com> wrote:
On Thu, Aug 5, 2021 at 3:54 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
Few more comments:
===================
1. Do we need the previous column 'puballtables' after adding pubtype
or pubkind in pg_publication?I felt this should be retained as old client will still be using puballtables, like in case of old client executing \dRp+ commands.
But do we guarantee that old clients work with newer server versions?
For example, psql docs say: "psql works best with servers of the same
or an older major version. Backslash commands are particularly likely
to fail if the server is of a newer version than psql itself." [1]
(See Notes Section). Similarly, pg_dump docs say: "However, pg_dump
cannot dump from PostgreSQL servers newer than its own major version;
it will refuse to even try, rather than risk making an invalid dump."
[2] (See Notes Section).It seems Sawada-San has the same question and IIUC docs suggest we
don't need such compatibility, so what makes you think we need it?
Ok, I was not sure if we can remove any system table columns, hence
had retained it. It seems that my understanding was wrong. I will
remove this column in the next version of the patch.
Regards,
Vignesh
On Fri, Aug 6, 2021 at 4:39 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 6, 2021 at 2:02 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 4, 2021 at 4:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Aug 3, 2021 at 8:38 PM vignesh C <vignesh21@gmail.com> wrote:
6. + {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */ + PublicationSchemaPsnspcidPspubidIndexId, + 2, + { + Anum_pg_publication_schema_psnspcid, + Anum_pg_publication_schema_pspubid, + 0, + 0 + },Why don't we keep pubid as the first column in this index?
I wanted to keep it similar to PUBLICATIONRELMAP, should we keep it as
it is, thoughts?Okay, I see your point. I think for PUBLICATIONRELMAP, we need it
because it is searched using the only relid in
GetRelationPublications, so, similarly, in the patch, you are using
schema_oid in GetSchemaPublications, so probably that will help. I was
wondering why you haven't directly used the cache in
GetSchemaPublications similar to GetRelationPublications?
Both of the approaches work, I was not sure which one is better, If
the approach in GetRelationPublications is better, I will change it to
something similar to GetRelationPublications. Thoughts?
It seems
there is a danger for concurrent object drop. Can you please check how
the safety is ensured say when either one wants to drop the
corresponding relation/schema or publication?
If a table is dropped concurrently from another session during logical
replication of some operation in that table, while we get
get_rel_sync_entry the cache invalidations(rel_sync_cache_relation_cb)
happen. The cache entry will be marked as false, also the schema_sent
will be marked as false. It will resend the relation using the
relation that was prepared while processing this transaction from
ReorderBufferProcessTXN. I felt this is ok since the relation is
dropped after the operation on the table. Similarly if the publication
is dropped concurrently from another session during logical
replication of some operation in that table, while we get
get_rel_sync_entry the cache
invalidations(publication_invalidation_cb) happen. The publications
will be reloaded and validated again, the data will be replicated to
the server. I felt this behavior is fine since the publication is
dropped after the operation on the table.
Another point is why
can't we use the other index (where the index is on relid or
schema_oid (PublicationSchemaObjectIndexId))?
I felt this cannot be used because In this case the index is in the
oid column of pg_publication_schema and not on psnspcid column.
Regards,
Vignesh
On Sun, Aug 8, 2021 at 2:52 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Aug 6, 2021 at 4:39 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 6, 2021 at 2:02 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 4, 2021 at 4:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Aug 3, 2021 at 8:38 PM vignesh C <vignesh21@gmail.com> wrote:
6. + {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */ + PublicationSchemaPsnspcidPspubidIndexId, + 2, + { + Anum_pg_publication_schema_psnspcid, + Anum_pg_publication_schema_pspubid, + 0, + 0 + },Why don't we keep pubid as the first column in this index?
I wanted to keep it similar to PUBLICATIONRELMAP, should we keep it as
it is, thoughts?Okay, I see your point. I think for PUBLICATIONRELMAP, we need it
because it is searched using the only relid in
GetRelationPublications, so, similarly, in the patch, you are using
schema_oid in GetSchemaPublications, so probably that will help. I was
wondering why you haven't directly used the cache in
GetSchemaPublications similar to GetRelationPublications?Both of the approaches work, I was not sure which one is better, If
the approach in GetRelationPublications is better, I will change it to
something similar to GetRelationPublications. Thoughts?
I think it is better to use the cache as if we don't find the entry in
the cache, then we will anyway search the required entry via sys table
scan, see SearchCatCacheList. I think the point I wanted to ensure was
that a concurrent session won't blow up the entry while we are looking
at it. How is that ensured?
--
With Regards,
Amit Kapila.
On Fri, Aug 6, 2021 at 2:00 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Wed, Aug 4, 2021 at 12:08 AM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Aug 3, 2021 at 12:00 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:On Monday, August 2, 2021 11:40 PM vignesh C <vignesh21@gmail.com>wrote:
Thanks for the comments, attached v17 patches has the fixes for the same.
Thanks for your new patch.
I saw the following warning when compiling. It seems we don't need this variable any more.
publicationcmds.c: In function ‘AlterPublicationSchemas’:
publicationcmds.c:592:15: warning: unused variable ‘oldlc’ [-Wunused-variable]
ListCell *oldlc;
^~~~~Thanks for reporting this, this is fixed in the v18 patch attached.
I've also started reviewing this patch. I've not looked at the patch
yet but here are initial comments/questions based on using this
feature:pg_publication catalog still has puballtables column but it's still
necessary? IIUC since pubtype = 'a' means publishing all tables in the
database puballtables seems no longer necessary.
I will remove puballtables in my next version of the patch.
---
Suppose that a parent table and its child table are defined in
different schemas, there is a publication for the schema where only
the parent table is defined, and the subscriber subscribes to the
publication, should changes for its child table be replicated to the
subscriber?
I felt that in this case only the table data that is present in the
publish schema should be sent to the subscriber. Since the child table
schema is not part of the publication, I felt this child table data
should not be replicated. Thoughts?
I have kept the above same behavior in the case of publication created
using PUBLISH_VIA_PARTITION_ROOT option i.e the child table data will
not be sent. But now I'm feeling we should send the child table data
since it is being sent through the parent table which is part of the
publication. Also this way users can use this option if the user has
the table having partitions designed across the schemas. Thoughts?
In FOR TABLE cases, i.g., where the subscriber subscribes to the
publication that is only for the parent table, changes for its child
table are replicated to the subscriber.As far as I tested v18 patch, changes for the child table are not
replicated in FOR SCHEMA cases. Here is the test script:On publisher and subscriber:
create schema p_schema;
create schema c_schema;
create table p_schema.p (a int) partition by list (a);
create table c_schema.c partition of p_schema.p for values in (1);On publisher:
create publication pub_p_schema for schema p_schema;On subscriber:
create subscription pub connection 'dbname=postgres' publication pub_p_schema;On publisher:
insert into p_schema.p values (1);
select * from p_schema.p;
a
---
1
(1 row)On subscriber:
select * from p_schema.p;
a
---(0 rows)
I have kept this behavior intentionally, details explained above. Thoughts?
Regards,
Vignesh
On Aug 6, 2021, at 1:32 AM, vignesh C <vignesh21@gmail.com> wrote:
the attached v19 patch
With v19 applied, a schema owner can publish the contents of a table regardless of ownership or permissions on that table:
+CREATE ROLE user1;
+GRANT CREATE ON DATABASE regression TO user1;
+CREATE ROLE user2;
+GRANT CREATE ON DATABASE regression TO user2;
+SET SESSION AUTHORIZATION user1;
+CREATE SCHEMA user1schema;
+GRANT CREATE, USAGE ON SCHEMA user1schema TO user2;
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION user2;
+CREATE TABLE user1schema.user2private (junk text);
+REVOKE ALL PRIVILEGES ON user1schema.user2private FROM PUBLIC;
+REVOKE ALL PRIVILEGES ON user1schema.user2private FROM user1;
+CREATE TABLE user1schema.user2public (junk text);
+GRANT SELECT ON user1schema.user2public TO PUBLIC;
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION user1;
+SELECT * FROM user1schema.user2private;
+ERROR: permission denied for table user2private
+SELECT * FROM user1schema.user2public;
+ junk
+------
+(0 rows)
+
+CREATE PUBLICATION user1pub;
+WARNING: wal_level is insufficient to publish logical changes
+HINT: Set wal_level to logical before creating subscriptions.
+ALTER PUBLICATION user1pub
+ ADD TABLE user1schema.user2public;
+ERROR: must be owner of table user2public
+ALTER PUBLICATION user1pub
+ ADD TABLE user1schema.user2private, user1schema.user2public;
+ERROR: must be owner of table user2private
+SELECT * FROM pg_catalog.pg_publication_tables
+ WHERE pubname = 'user1pub';
+ pubname | schemaname | tablename
+---------+------------+-----------
+(0 rows)
+
+ALTER PUBLICATION user1pub ADD SCHEMA user1schema;
+SELECT * FROM pg_catalog.pg_publication_tables
+ WHERE pubname = 'user1pub';
+ pubname | schemaname | tablename
+----------+-------------+--------------
+ user1pub | user1schema | user2private
+ user1pub | user1schema | user2public
+(2 rows)
It is a bit counterintuitive that schema owners do not have administrative privileges over tables within their schemas, but that's how it is. The design of this patch seems to assume otherwise. Perhaps ALTER PUBLICATION ... ADD SCHEMA should be restricted to superusers, just as FOR ALL TABLES?
Alternatively, you could add ownership checks per table to mirror the behavior of ALTER PUBLICATION ... ADD TABLE, but that would foreclose the option of automatically updating the list of tables in the publication as new tables are added to the schema, since those new tables would not necessarily belong to the schema owner, and having a error thrown during CREATE TABLE would be quite unfriendly. I think until this is hammered out, it is safer to require superuser privileges and then we can revisit this issue and loosen the requirement in a subsequent commit.
What do you think?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Mon, Aug 9, 2021 at 9:50 PM Mark Dilger <mark.dilger@enterprisedb.com> wrote:
On Aug 6, 2021, at 1:32 AM, vignesh C <vignesh21@gmail.com> wrote:
the attached v19 patch
With v19 applied, a schema owner can publish the contents of a table regardless of ownership or permissions on that table:
...
...
It is a bit counterintuitive that schema owners do not have administrative privileges over tables within their schemas, but that's how it is. The design of this patch seems to assume otherwise. Perhaps ALTER PUBLICATION ... ADD SCHEMA should be restricted to superusers, just as FOR ALL TABLES?
+1. Your suggestion sounds reasonable to me.
Alternatively, you could add ownership checks per table to mirror the behavior of ALTER PUBLICATION ... ADD TABLE, but that would foreclose the option of automatically updating the list of tables in the publication as new tables are added to the schema, since those new tables would not necessarily belong to the schema owner, and having a error thrown during CREATE TABLE would be quite unfriendly. I think until this is hammered out, it is safer to require superuser privileges and then we can revisit this issue and loosen the requirement in a subsequent commit.
I think the same argument can be made for "FOR ALL TABLES .." as well.
So, let's leave such a requirement for another patch.
--
With Regards,
Amit Kapila.
On Fri, Aug 6, 2021 at 6:32 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v19 patch has the fixes for the comments.
Some more review comments, this time for the v19 patch:
(1) In patch v19-0002, there's still a couple of instances where it
says "publication type" instead of "publication kind".
(2) src/backend/catalog/pg_publication.c
"This should only be used for normal publications."
What exactly is meant by that - what type is considered normal? Maybe
that comment should be more specific.
(3) src/backend/catalog/pg_publication.c
GetSchemaPublications
Function header says "Gets list of publication oids for publications
marked as FOR SCHEMA."
Shouldn't it say something like: "Gets the list of FOR SCHEMA
publication oids associated with a specified schema oid." or something
like that?
(since the function accepts a schemaid parameter)
(4) src/backend/commands/publicationcmds.c
In AlterPublicationSchemas(), I notice that the two error cases
"cannot be added to or dropped ..." don't check stmt->action for
DEFELEM_ADD/DEFELEM_DROP.
Is that OK? (i.e. should these cases error out if stmt->action is not
DEFELEM_ADD/DEFELEM_DROP?)
Also, I assume that the else part (not DEFELEM_ADD/DEFELEM_DROP) is
the "Set" case? Maybe a comment should be added to the top of the else
part.
(5) src/backend/commands/publicationcmds.c
Typo (same in 2 places): "automaically" -> "automatically"
+ * will be released automaically at the end of create publication
See functions:
(i) CreatePublication
(ii) AlterPublicationSchemas
(6) src/backend/commands/publicationcmds.c
LockSchemaList
Function header says "are locked in ShareUpdateExclusiveLock mode" but
then code calls LockDatabaseObject using "AccessShareLock".
Regards,
Greg Nancarrow
Fujitsu Australia
On Mon, Aug 9, 2021 at 11:31 AM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Aug 6, 2021 at 2:00 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
---
Suppose that a parent table and its child table are defined in
different schemas, there is a publication for the schema where only
the parent table is defined, and the subscriber subscribes to the
publication, should changes for its child table be replicated to the
subscriber?I felt that in this case only the table data that is present in the
publish schema should be sent to the subscriber. Since the child table
schema is not part of the publication, I felt this child table data
should not be replicated.
But, as point out by Sawada-San, the same is true for FOR TABLE case.
I think we should be consistent here and should publish the data for
the child table if the parent table's schema is published.
I have kept the above same behavior in the case of publication created
using PUBLISH_VIA_PARTITION_ROOT option i.e the child table data will
not be sent. But now I'm feeling we should send the child table data
since it is being sent through the parent table which is part of the
publication. Also this way users can use this option if the user has
the table having partitions designed across the schemas.
This sounds fine to me.
--
With Regards,
Amit Kapila.
On Mon, Aug 9, 2021 at 9:50 PM Mark Dilger <mark.dilger@enterprisedb.com> wrote:
On Aug 6, 2021, at 1:32 AM, vignesh C <vignesh21@gmail.com> wrote:
the attached v19 patch
With v19 applied, a schema owner can publish the contents of a table regardless of ownership or permissions on that table:
+CREATE ROLE user1; +GRANT CREATE ON DATABASE regression TO user1; +CREATE ROLE user2; +GRANT CREATE ON DATABASE regression TO user2; +SET SESSION AUTHORIZATION user1; +CREATE SCHEMA user1schema; +GRANT CREATE, USAGE ON SCHEMA user1schema TO user2; +RESET SESSION AUTHORIZATION; +SET SESSION AUTHORIZATION user2; +CREATE TABLE user1schema.user2private (junk text); +REVOKE ALL PRIVILEGES ON user1schema.user2private FROM PUBLIC; +REVOKE ALL PRIVILEGES ON user1schema.user2private FROM user1; +CREATE TABLE user1schema.user2public (junk text); +GRANT SELECT ON user1schema.user2public TO PUBLIC; +RESET SESSION AUTHORIZATION; +SET SESSION AUTHORIZATION user1; +SELECT * FROM user1schema.user2private; +ERROR: permission denied for table user2private +SELECT * FROM user1schema.user2public; + junk +------ +(0 rows) + +CREATE PUBLICATION user1pub; +WARNING: wal_level is insufficient to publish logical changes +HINT: Set wal_level to logical before creating subscriptions. +ALTER PUBLICATION user1pub + ADD TABLE user1schema.user2public; +ERROR: must be owner of table user2public +ALTER PUBLICATION user1pub + ADD TABLE user1schema.user2private, user1schema.user2public; +ERROR: must be owner of table user2private +SELECT * FROM pg_catalog.pg_publication_tables + WHERE pubname = 'user1pub'; + pubname | schemaname | tablename +---------+------------+----------- +(0 rows) + +ALTER PUBLICATION user1pub ADD SCHEMA user1schema; +SELECT * FROM pg_catalog.pg_publication_tables + WHERE pubname = 'user1pub'; + pubname | schemaname | tablename +----------+-------------+-------------- + user1pub | user1schema | user2private + user1pub | user1schema | user2public +(2 rows)It is a bit counterintuitive that schema owners do not have administrative privileges over tables within their schemas, but that's how it is. The design of this patch seems to assume otherwise. Perhaps ALTER PUBLICATION ... ADD SCHEMA should be restricted to superusers, just as FOR ALL TABLES?
I will handle this in the next version of the patch.
Additionally I will add this check for "Alter publication add schema"
and "Alter publication set schema". I'm not planning to add this check
for "Alter publication drop schema" to keep the behavior similar to
"Alter publication drop table".
Also, the behavior of "Alter publication drop table" for which the
user is not the owner is successful, Is this behavior correct?
create table tbl1(c1 int);
create table tbl2(c1 int);
create publication mypub1 for table tbl1,tbl2;
SET SESSION AUTHORIZATION user2;
alter table tbl2 owner to user2;
RESET SESSION AUTHORIZATION;
postgres=> alter publication mypub1 drop table tbl2;
ALTER PUBLICATION
postgres=> alter publication mypub1 add table tbl2;
ERROR: must be owner of table tbl2
Thoughts?
Regards,
Vignesh
On Aug 10, 2021, at 10:59 PM, vignesh C <vignesh21@gmail.com> wrote:
Also, the behavior of "Alter publication drop table" for which the
user is not the owner is successful, Is this behavior correct?
I think that dropping a table from a publication should be allowed for the publication owner, without regard to the owner of the table. Adding a table to a publication is different, as it exposes the contents of the table.
Consider the following:
+create user user1;
+create user user2;
+create table tbl1(c1 int);
+create table tbl2(c1 int);
+create publication pub1 for table tbl1,tbl2;
+WARNING: wal_level is insufficient to publish logical changes
+HINT: Set wal_level to logical before creating subscriptions.
+alter table tbl1 owner to user1;
+alter publication pub1 owner to user1;
+alter table tbl2 owner to user2;
+SET SESSION AUTHORIZATION user2;
+alter publication pub1 drop table tbl1;
+ERROR: must be owner of publication pub1
+alter publication pub1 drop table tbl2;
+ERROR: must be owner of publication pub1
+alter publication pub1 add table tbl1;
+ERROR: must be owner of publication pub1
+alter publication pub1 add table tbl2;
+ERROR: must be owner of publication pub1
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION user1;
+alter publication pub1 drop table tbl1;
+alter publication pub1 drop table tbl2;
+alter publication pub1 add table tbl1;
+alter publication pub1 add table tbl2;
+ERROR: must be owner of table tbl2
Clearly user2 cannot modify pub1, not even with respect to user2's own table. user1 can modify its own publication except for adding someone else's table. This seems correct to me.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Wed, Aug 11, 2021 at 7:45 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
On Aug 10, 2021, at 10:59 PM, vignesh C <vignesh21@gmail.com> wrote:
Also, the behavior of "Alter publication drop table" for which the
user is not the owner is successful, Is this behavior correct?I think that dropping a table from a publication should be allowed for the publication owner, without regard to the owner of the table.
Adding a table to a publication is different, as it exposes the contents of the table.
..
..
Clearly user2 cannot modify pub1, not even with respect to user2's own table. user1 can modify its own publication except for adding someone else's table. This seems correct to me.
That looks reasonable to me as well and I think we should follow the
same for the schema.
--
With Regards,
Amit Kapila.
On Mon, Aug 9, 2021 at 1:53 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Sun, Aug 8, 2021 at 2:52 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Aug 6, 2021 at 4:39 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 6, 2021 at 2:02 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 4, 2021 at 4:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Aug 3, 2021 at 8:38 PM vignesh C <vignesh21@gmail.com> wrote:
6. + {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */ + PublicationSchemaPsnspcidPspubidIndexId, + 2, + { + Anum_pg_publication_schema_psnspcid, + Anum_pg_publication_schema_pspubid, + 0, + 0 + },Why don't we keep pubid as the first column in this index?
I wanted to keep it similar to PUBLICATIONRELMAP, should we keep it as
it is, thoughts?Okay, I see your point. I think for PUBLICATIONRELMAP, we need it
because it is searched using the only relid in
GetRelationPublications, so, similarly, in the patch, you are using
schema_oid in GetSchemaPublications, so probably that will help. I was
wondering why you haven't directly used the cache in
GetSchemaPublications similar to GetRelationPublications?Both of the approaches work, I was not sure which one is better, If
the approach in GetRelationPublications is better, I will change it to
something similar to GetRelationPublications. Thoughts?I think it is better to use the cache as if we don't find the entry in
the cache, then we will anyway search the required entry via sys table
scan, see SearchCatCacheList.
+1
I had the same comment while reading the v19 patch.
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Fri, Aug 6, 2021 at 5:33 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v19 patch has the fixes for the comments.
Thank you for updating the patch!
Here are some comments on v19 patch:
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ psoid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
Since RemovePublicationSchemaById() does simple catalog tuple
deletion, it seems to me that we can DropObjectById() to delete the
row of pg_publication_schema.
---
{
- ScanKeyInit(&key[0],
+ ScanKeyData skey[1];
+
+ ScanKeyInit(&skey[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key);
+ scan = table_beginscan_catalog(classRel, 1, skey);
Since we specify 1 as the number of keys in table_beginscan_catalog(),
can we reuse 'key' instead of using 'skey'?
---
Even if we drop all tables added to the publication from it, 'pubkind'
doesn't go back to 'empty'. Is that intentional behavior? If we do
that, we can save the lookup of pg_publication_rel and
pg_publication_schema in some cases, and we can switch the publication
that was created as FOR SCHEMA to FOR TABLE and vice versa.
---
+static void
+UpdatePublicationKindTupleValue(Relation rel, HeapTuple tup, int col,
+ char pubkind)
Since all callers of this function specify Anum_pg_publication_pubkind
to 'col', it seems 'col' is not necessary.
---
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup,
Form_pg_publication pubform)
I think we don't need to pass 'pubform' to this function since we can
get it by GETSTRUCT(tup).
---
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+ List *schemaPubids = GetSchemaPublications(schemaId);
Can we defer to get the list of schema publications (i.g.,
'schemaPubids') until we find the PUBKIND_SCHEMA publication? Perhaps
the same is true for building 'pubids'.
---
+ List of publications
+ Name | Owner | All tables | Inserts
| Updates | Deletes | Truncates | Via root | PubKind
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t
| f | f | f | f | e
+ testpub_default | regress_publication_user | f | f
| t | f | f | f | e
I think it's more readable if \dRp shows 'all tables', 'table',
'schema', and 'empty' in PubKind instead of the single character.
I think 'Pub kind' is more consistent with other column names.
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Thu, Aug 12, 2021 at 5:54 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Fri, Aug 6, 2021 at 5:33 PM vignesh C <vignesh21@gmail.com> wrote:
---
Even if we drop all tables added to the publication from it, 'pubkind'
doesn't go back to 'empty'. Is that intentional behavior? If we do
that, we can save the lookup of pg_publication_rel and
pg_publication_schema in some cases, and we can switch the publication
that was created as FOR SCHEMA to FOR TABLE and vice versa.
Do we really want to allow users to change a publication that is FOR
SCHEMA to FOR TABLE? I see that we don't allow to do that FOR TABLES.
postgres=# Alter Publication pub add table tbl1;
ERROR: publication "pub" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
--
With Regards,
Amit Kapila.
On 13.08.21 04:59, Amit Kapila wrote:
Even if we drop all tables added to the publication from it, 'pubkind'
doesn't go back to 'empty'. Is that intentional behavior? If we do
that, we can save the lookup of pg_publication_rel and
pg_publication_schema in some cases, and we can switch the publication
that was created as FOR SCHEMA to FOR TABLE and vice versa.Do we really want to allow users to change a publication that is FOR
SCHEMA to FOR TABLE? I see that we don't allow to do that FOR TABLES.
postgres=# Alter Publication pub add table tbl1;
ERROR: publication "pub" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
I think the strict separation between publication-for-tables vs.
publication-for-schemas is a mistake. Why can't I have a publication
that publishes tables t1, t2, t3, *and* schemas s1, s2, s3. Also note
that we have a pending patch to add sequences support to logical
replication. So eventually, a publication will be able to contain a
bunch of different objects of different kinds.
On Sat, Aug 14, 2021 at 3:02 PM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
On 13.08.21 04:59, Amit Kapila wrote:
Even if we drop all tables added to the publication from it, 'pubkind'
doesn't go back to 'empty'. Is that intentional behavior? If we do
that, we can save the lookup of pg_publication_rel and
pg_publication_schema in some cases, and we can switch the publication
that was created as FOR SCHEMA to FOR TABLE and vice versa.Do we really want to allow users to change a publication that is FOR
SCHEMA to FOR TABLE? I see that we don't allow to do that FOR TABLES.
postgres=# Alter Publication pub add table tbl1;
ERROR: publication "pub" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.I think the strict separation between publication-for-tables vs.
publication-for-schemas is a mistake. Why can't I have a publication
that publishes tables t1, t2, t3, *and* schemas s1, s2, s3. Also note
that we have a pending patch to add sequences support to logical
replication. So eventually, a publication will be able to contain a
bunch of different objects of different kinds.
Valid point.
--
With Regards,
Amit Kapila.
On Fri, Aug 6, 2021 at 2:00 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Wed, Aug 4, 2021 at 12:08 AM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Aug 3, 2021 at 12:00 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:On Monday, August 2, 2021 11:40 PM vignesh C <vignesh21@gmail.com>wrote:
Thanks for the comments, attached v17 patches has the fixes for the same.
Thanks for your new patch.
I saw the following warning when compiling. It seems we don't need this variable any more.
publicationcmds.c: In function ‘AlterPublicationSchemas’:
publicationcmds.c:592:15: warning: unused variable ‘oldlc’ [-Wunused-variable]
ListCell *oldlc;
^~~~~Thanks for reporting this, this is fixed in the v18 patch attached.
I've also started reviewing this patch. I've not looked at the patch
yet but here are initial comments/questions based on using this
feature:pg_publication catalog still has puballtables column but it's still
necessary? IIUC since pubtype = 'a' means publishing all tables in the
database puballtables seems no longer necessary.
Removed puballtables.
---
Suppose that a parent table and its child table are defined in
different schemas, there is a publication for the schema where only
the parent table is defined, and the subscriber subscribes to the
publication, should changes for its child table be replicated to the
subscriber?In FOR TABLE cases, i.g., where the subscriber subscribes to the
publication that is only for the parent table, changes for its child
table are replicated to the subscriber.
Modified it to keep the behavior similar to FOR Table publication.
As far as I tested v18 patch, changes for the child table are not
replicated in FOR SCHEMA cases. Here is the test script:On publisher and subscriber:
create schema p_schema;
create schema c_schema;
create table p_schema.p (a int) partition by list (a);
create table c_schema.c partition of p_schema.p for values in (1);On publisher:
create publication pub_p_schema for schema p_schema;On subscriber:
create subscription pub connection 'dbname=postgres' publication pub_p_schema;On publisher:
insert into p_schema.p values (1);
select * from p_schema.p;
a
---
1
(1 row)On subscriber:
select * from p_schema.p;
a
---(0 rows)
Modified to handle this.
Thanks for the comments, the attached v20 patch handles the above issues.
Regards,
Vignesh
Attachments:
v20-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v20-0001-Added-schema-level-support-for-publication.patchDownload
From 31742279f4f2c846cdd952487c136a5e354d9990 Mon Sep 17 00:00:00 2001
From: Vigneshwaran c <vignesh21@gmail.com>
Date: Mon, 26 Jul 2021 09:25:22 +0530
Subject: [PATCH v20 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new schema option allows one or more schemas to be specified, whose tables
are selected by the publisher for sending the data to the subscriber.
pg_publication maintains information about the publication. Previously, the
"puballtables" bool column was used to indicate if the publication was the
"FOR ALL TABLES" kind (if true) or the "FOR TABLE" kind (if false). With the
introduction of the "FOR SCHEMA" publication kind, it is not easy to determine
the publication kind. Therefore, a new column "pubkind" has been added to the
pg_publication relation to indicate the publication kind.
There was the possibility of avoiding addition of this new column, but that
would require checking puballtables of pg_publication and checking
pg_publication_rel for table kind publication and then checking
pg_publication_schema for schema kind publication. Instead, I preferred to add
the "pubkind" column, which makes things easier, and also will help support
new options in the future.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber. Changes were made
to pg_dump to handle pubkind updation in the pg_publication table when the
database is upgraded.
Prototypes present in pg_publication.h have been moved to publicationcmds.h so
that minimal data structures are exported to pg_dump and psql clients, as the
rest of the information need not be exported.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 142 ++++++++
src/backend/catalog/pg_publication.c | 333 ++++++++++++++++--
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 370 ++++++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 5 +-
src/backend/nodes/copyfuncs.c | 2 +-
src/backend/nodes/equalfuncs.c | 2 +-
src/backend/parser/gram.y | 126 +++++--
src/backend/replication/pgoutput/pgoutput.c | 26 +-
src/backend/utils/cache/relcache.c | 5 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 163 ++++++++-
src/bin/pg_dump/pg_dump.h | 16 +
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 276 ++++++++++++---
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 64 ++--
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 53 +++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 24 +-
src/include/utils/rel.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 112 +++---
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/publication.sql | 2 +-
src/tools/pgindent/typedefs.list | 5 +
36 files changed, 1611 insertions(+), 249 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..4cf144eef0 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -67,6 +68,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
+#include "commands/publicationcmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
@@ -829,6 +831,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +881,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1126,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1947,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2353,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2847,6 +2902,49 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ *pubname = get_publication_name(psform->pspubid, false);
+ *nspname = get_namespace_name(psform->psnspcid);
+ if (!(*nspname))
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3902,6 +4000,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4590,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5829,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..760e3c762c 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -214,6 +216,96 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schemas cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schemas cannot be added to publications.")));
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ schemaRels = GetSchemaPublicationRelations(schemaoid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -238,10 +330,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,36 +399,79 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
+ systable_endscan(scan);
+ table_close(pubrelsrel, AccessShareLock);
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
+ return result;
+}
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
}
systable_endscan(scan);
- table_close(pubrelsrel, AccessShareLock);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of FOR SCHEMA publication oids associated with a specified
+ * schema oid
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONSCHEMAMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_schema) GETSTRUCT(tup))->pspubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
return result;
}
@@ -320,9 +492,9 @@ GetAllTablesPublications(void)
rel = table_open(PublicationRelationId, AccessShareLock);
ScanKeyInit(&scankey,
- Anum_pg_publication_puballtables,
- BTEqualStrategyNumber, F_BOOLEQ,
- BoolGetDatum(true));
+ Anum_pg_publication_pubkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(PUBKIND_ALLTABLES));
scan = systable_beginscan(rel, InvalidOid, false,
NULL, 1, &scankey);
@@ -342,7 +514,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -404,6 +576,96 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets list of relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaOid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(schemaOid != InvalidOid);
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ /* Skip the relations which are not publishable */
+ if (!is_publishable_class(relForm->oid, relForm))
+ continue;
+
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaOid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -425,12 +687,12 @@ GetPublication(Oid pubid)
pub = (Publication *) palloc(sizeof(Publication));
pub->oid = pubid;
pub->name = pstrdup(NameStr(pubform->pubname));
- pub->alltables = pubform->puballtables;
pub->pubactions.pubinsert = pubform->pubinsert;
pub->pubactions.pubupdate = pubform->pubupdate;
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubviaroot = pubform->pubviaroot;
+ pub->pubkind = pubform->pubkind;
ReleaseSysCache(tup);
@@ -530,13 +792,20 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* replicated using leaf partition identity and schema, so we only
* need those.
*/
- if (publication->alltables)
+ if (publication->pubkind == PUBKIND_ALLTABLES)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
- else
+ else if (publication->pubkind == PUBKIND_TABLE)
tables = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ else if (publication->pubkind == PUBKIND_SCHEMA)
+ tables = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+ else
+ tables = NIL;
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..35f47d3253 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..1de6c186b3 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -34,25 +36,26 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +141,47 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
/*
* Create new publication.
*/
@@ -168,6 +212,12 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create FOR ALL TABLES publication")));
+ /* FOR SCHEMA requires superuser */
+ if (stmt->schemas && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR SCHEMA publication")));
+
rel = table_open(PublicationRelationId, RowExclusiveLock);
/* Check if name is used */
@@ -198,8 +248,6 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
puboid = GetNewOidWithIndex(rel, PublicationObjectIndexId,
Anum_pg_publication_oid);
values[Anum_pg_publication_oid - 1] = ObjectIdGetDatum(puboid);
- values[Anum_pg_publication_puballtables - 1] =
- BoolGetDatum(stmt->for_all_tables);
values[Anum_pg_publication_pubinsert - 1] =
BoolGetDatum(pubactions.pubinsert);
values[Anum_pg_publication_pubupdate - 1] =
@@ -211,6 +259,15 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
+ if (stmt->schemas)
+ values[Anum_pg_publication_pubkind - 1] = PUBKIND_SCHEMA;
+ else if (stmt->tables)
+ values[Anum_pg_publication_pubkind - 1] = PUBKIND_TABLE;
+ else if (stmt->for_all_tables)
+ values[Anum_pg_publication_pubkind - 1] = PUBKIND_ALLTABLES;
+ else
+ values[Anum_pg_publication_pubkind - 1] = PUBKIND_EMPTY;
+
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
/* Insert tuple into catalog. */
@@ -224,6 +281,24 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ if (stmt->schemas)
+ {
+ List *schemaoidlist = NIL;
+
+ Assert(list_length(stmt->schemas) > 0);
+
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of create publication
+ * command.
+ */
+ LockSchemaList(schemaoidlist);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ }
+
if (stmt->tables)
{
List *rels;
@@ -250,6 +325,34 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
return myself;
}
+/*
+ * Update publication kind in pg_publication relation.
+ */
+static void
+UpdatePublicationKindTupleValue(Relation rel, HeapTuple tup, char pubkind)
+{
+ bool nulls[Natts_pg_publication];
+ bool replaces[Natts_pg_publication];
+ Datum values[Natts_pg_publication];
+
+
+ /* Everything ok, form a new tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[Anum_pg_publication_pubkind - 1] = pubkind;
+ replaces[Anum_pg_publication_pubkind - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+ replaces);
+
+ /* Update the catalog */
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ CommandCounterIncrement();
+}
+
/*
* Change options of a publication.
*/
@@ -310,37 +413,27 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate the relcache. */
- if (pubform->puballtables)
+ if (pubform->pubkind == PUBKIND_ALLTABLES)
{
CacheInvalidateRelcacheAll();
}
else
{
+ List *relids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
-
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
-
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ if (pubform->pubkind == PUBKIND_TABLE)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ else if (pubform->pubkind == PUBKIND_SCHEMA)
+ relids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
@@ -362,20 +455,31 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Oid pubid = pubform->oid;
/* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
+ if (pubform->pubkind == PUBKIND_ALLTABLES)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ errdetail("Tables cannot be added, dropped or set on FOR ALL TABLES publications.")));
+
+ if (pubform->pubkind == PUBKIND_SCHEMA)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR SCHEMA",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added, dropped or set on FOR SCHEMA publications.")));
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
- if (stmt->tableAction == DEFELEM_ADD)
+ if (stmt->action == DEFELEM_ADD)
+ {
+ if (pubform->pubkind == PUBKIND_EMPTY)
+ UpdatePublicationKindTupleValue(rel, tup, PUBKIND_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -384,6 +488,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ if (pubform->pubkind == PUBKIND_EMPTY)
+ UpdatePublicationKindTupleValue(rel, tup, PUBKIND_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -426,11 +533,84 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
+{
+ List *schemaoidlist = NIL;
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->pubkind == PUBKIND_ALLTABLES)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added, dropped or set on FOR ALL TABLES publications.")));
+
+ if (pubform->pubkind == PUBKIND_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR TABLE",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added, dropped or set on FOR TABLE publications.")));
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /* Convert the text list into oid list */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks will
+ * be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaoidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ if (pubform->pubkind == PUBKIND_EMPTY)
+ UpdatePublicationKindTupleValue(rel, tup, PUBKIND_SCHEMA);
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ if (pubform->pubkind == PUBKIND_EMPTY)
+ UpdatePublicationKindTupleValue(rel, tup, PUBKIND_SCHEMA);
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaoidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +639,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +679,57 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_schema pubsch;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ schemaRels = GetSchemaPublicationRelations(pubsch->psnspcid,
+ PUBLICATION_PART_ALL);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+ foreach(lc, schemalist)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaoid, 0, AccessShareLock);
+ }
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -607,7 +840,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->schemas));
foreach(lc, rels)
{
@@ -631,6 +864,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->tables));
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +931,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
@@ -696,7 +996,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- if (form->puballtables && !superuser_arg(newOwnerId))
+ if (form->pubkind == PUBKIND_ALLTABLES && !superuser_arg(newOwnerId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of publication \"%s\"",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b18de38e73..29a623f34d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -12267,6 +12268,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15846,7 +15848,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..b0d19cabc3 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4823,7 +4823,7 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(tables);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..f4f7e896dd 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2309,7 +2309,7 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(tables);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 39a2849eba..6fac2a4c30 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +428,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,6 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <schemaspec> SchemaSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,45 +9593,68 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
$$ = (Node *)n;
}
- ;
-
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR TABLE relation_expr_list opt_definition
{
- $$ = (Node *) $3;
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->tables = (List *)$6;
+ $$ = (Node *)n;
}
- | FOR ALL TABLES
+ | CREATE PUBLICATION name FOR SCHEMA schema_list opt_definition
{
- $$ = (Node *) makeInteger(true);
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->schemas = (List *)$6;
+ $$ = (Node *)n;
}
;
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9641,6 +9666,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9656,7 +9686,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
| ALTER PUBLICATION name SET TABLE relation_expr_list
@@ -9664,7 +9694,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
| ALTER PUBLICATION name DROP TABLE relation_expr_list
@@ -9672,7 +9702,31 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->action = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name ADD_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->action = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->action = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $6;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -16621,6 +16675,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..d3f529de9a 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -15,7 +15,9 @@
#include "access/tupconvert.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_schema.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "fmgr.h"
#include "replication/logical.h"
#include "replication/logicalproto.h"
@@ -1068,6 +1070,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1151,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1172,12 +1185,21 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Publication *pub = lfirst(lc);
bool publish = false;
- if (pub->alltables)
+ if (pub->pubkind == PUBKIND_ALLTABLES)
{
publish = true;
if (pub->pubviaroot && am_partition)
publish_as_relid = llast_oid(get_partition_ancestors(relid));
}
+ else if (pub->pubkind == PUBKIND_SCHEMA)
+ {
+ if (list_member_oid(schemaPubids, pub->oid))
+ {
+ publish = true;
+ if (pub->pubviaroot && am_partition)
+ publish_as_relid = llast_oid(get_partition_ancestors(relid));
+ }
+ }
if (!publish)
{
@@ -1203,6 +1225,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..2ec805eefe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
+#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -5447,6 +5448,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5480,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..3010485f47 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..d0806b5a6e 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"
@@ -1630,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3950,6 +3955,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubviaroot;
+ int i_pubkind;
int i,
ntups;
@@ -3964,25 +3970,37 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 150000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "false AS puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubkind "
"FROM pg_publication p",
username_subquery);
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBuffer(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "(%s p.pubowner) AS rolname, "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot, "
+ "NULL AS pubkind FROM pg_publication p",
+ username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
- "FROM pg_publication p",
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, false AS pubviaroot, "
+ "NULL AS pubkind FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot, NULL AS pubkind "
"FROM pg_publication p",
username_subquery);
@@ -4000,6 +4018,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubviaroot = PQfnumber(res, "pubviaroot");
+ i_pubkind = PQfnumber(res, "pubkind");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4024,6 +4043,7 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+ pubinfo[i].pubkind = get_publication_kind(PQgetvalue(res, i, i_pubkind));
if (strlen(pubinfo[i].rolname) == 0)
pg_log_warning("owner of publication \"%s\" appears to be invalid",
@@ -4066,7 +4086,8 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBuffer(query, "CREATE PUBLICATION %s",
qpubname);
- if (pubinfo->puballtables)
+ if ((fout->remoteVersion >= 150000 && pubinfo->pubkind == PUBKIND_ALLTABLES) ||
+ (fout->remoteVersion < 150000 && pubinfo->puballtables))
appendPQExpBufferStr(query, " FOR ALL TABLES");
appendPQExpBufferStr(query, " WITH (publish = '");
@@ -4133,6 +4154,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pspubid;
+ int i_psnspcid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pspubid, psnspcid "
+ "FROM pg_catalog.pg_publication_schema");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pspubid = PQfnumber(res, "pspubid");
+ i_psnspcid = PQfnumber(res, "psnspcid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, i, i_pspubid));
+ Oid psnspcid = atooid(PQgetvalue(res, i, i_psnspcid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(psnspcid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4220,6 +4329,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10445,6 +10592,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18693,6 +18843,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..daad2802cd 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -616,6 +618,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubviaroot;
+ char pubkind;
} PublicationInfo;
/*
@@ -629,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8333558bda..f8b16887f4 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_publication.h"
#include "common.h"
#include "common/logging.h"
#include "describe.h"
@@ -3147,17 +3148,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid AND p.pubkind = 's'\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE p.pubkind = 't' AND pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.pubkind = 'a' \n"
+ " AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5045,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5087,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6163,14 +6246,17 @@ listPublications(const char *pattern)
printfPQExpBuffer(&buf,
"SELECT pubname AS \"%s\",\n"
- " pg_catalog.pg_get_userbyid(pubowner) AS \"%s\",\n"
- " puballtables AS \"%s\",\n"
- " pubinsert AS \"%s\",\n"
+ " pg_catalog.pg_get_userbyid(pubowner) AS \"%s\"",
+ gettext_noop("Name"),
+ gettext_noop("Owner"));
+ if (pset.sversion < 150000)
+ appendPQExpBuffer(&buf,
+ ",\n puballtables AS \"%s\"",
+ gettext_noop("All tables"));
+ appendPQExpBuffer(&buf,
+ ",\n pubinsert AS \"%s\",\n"
" pubupdate AS \"%s\",\n"
" pubdelete AS \"%s\"",
- gettext_noop("Name"),
- gettext_noop("Owner"),
- gettext_noop("All tables"),
gettext_noop("Inserts"),
gettext_noop("Updates"),
gettext_noop("Deletes"));
@@ -6182,6 +6268,19 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
+ if (pset.sversion >= 150000)
+ appendPQExpBuffer(&buf,
+ ",\n CASE pubkind "
+ " WHEN 'e' THEN '%s'"
+ " WHEN 'a' THEN '%s'"
+ " WHEN 't' THEN '%s'"
+ " WHEN 's' THEN '%s'"
+ " END as \"%s\"",
+ gettext_noop("empty"),
+ gettext_noop("all tables"),
+ gettext_noop("table"),
+ gettext_noop("schema"),
+ gettext_noop("Pub Kind"));
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6210,6 +6309,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6359,10 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubkind;
+ bool has_puballtables;
+ PQExpBufferData title;
+ printTableContent cont;
if (pset.sversion < 100000)
{
@@ -6237,19 +6376,35 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubkind = (pset.sversion >= 150000);
+ has_puballtables = (pset.sversion < 150000);
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
"SELECT oid, pubname,\n"
- " pg_catalog.pg_get_userbyid(pubowner) AS owner,\n"
- " puballtables, pubinsert, pubupdate, pubdelete");
+ " pg_catalog.pg_get_userbyid(pubowner) AS owner");
+
+ if (!has_puballtables)
+ appendPQExpBufferStr(&buf,
+ ", false as puballtables");
+ else
+ appendPQExpBufferStr(&buf,
+ ", puballtables");
+
+ appendPQExpBufferStr(&buf,
+ ", pubinsert, pubupdate, pubdelete");
+
if (has_pubtruncate)
appendPQExpBufferStr(&buf,
", pubtruncate");
if (has_pubviaroot)
appendPQExpBufferStr(&buf,
", pubviaroot");
+ if (has_pubkind)
+ appendPQExpBufferStr(&buf,
+ ", pubkind");
+
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6285,29 +6440,33 @@ describePublications(const char *pattern)
for (i = 0; i < PQntuples(res); i++)
{
const char align = 'l';
- int ncols = 5;
+ int ncols = 4;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
- bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
+ bool puballtables;
+ char pubkind = PUBKIND_EMPTY;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubkind)
+ ncols++;
+ if (has_puballtables)
+ {
+ puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ ncols++;
+ }
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
printTableInit(&cont, &myopt, title.data, ncols, nrows);
printTableAddHeader(&cont, gettext_noop("Owner"), true, align);
- printTableAddHeader(&cont, gettext_noop("All tables"), true, align);
+ if (has_puballtables)
+ printTableAddHeader(&cont, gettext_noop("All tables"), true, align);
printTableAddHeader(&cont, gettext_noop("Inserts"), true, align);
printTableAddHeader(&cont, gettext_noop("Updates"), true, align);
printTableAddHeader(&cont, gettext_noop("Deletes"), true, align);
@@ -6315,9 +6474,12 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
+ if (has_pubkind)
+ printTableAddHeader(&cont, gettext_noop("Pub kind"), true, align);
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
- printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
+ if (has_puballtables)
+ printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 4), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false);
@@ -6325,8 +6487,18 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubkind)
+ {
+ char *kind = PQgetvalue(res, i, 9);
- if (!puballtables)
+ pubkind = get_publication_kind(kind);
+ printTableAddCell(&cont, get_publication_kind_string(kind), false,
+ false);
+ }
+
+ /* Prior to version 15 check was based on all tables */
+ if ((has_pubkind && pubkind == PUBKIND_TABLE) ||
+ (has_puballtables && !puballtables))
{
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
@@ -6337,31 +6509,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
-
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
+ }
+ else if (has_pubkind && pubkind == PUBKIND_SCHEMA)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6535,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0750f70273..ae9cd8fe9d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2641,15 +2650,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..74ad444cb0 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,7 +18,6 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
/* ----------------
@@ -34,12 +33,6 @@ CATALOG(pg_publication,6104,PublicationRelationId)
Oid pubowner BKI_LOOKUP(pg_authid); /* publication owner */
- /*
- * indicates that this is special publication which should encompass all
- * tables in the database (except for the unlogged and temp ones)
- */
- bool puballtables;
-
/* true if inserts are published */
bool pubinsert;
@@ -54,6 +47,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* see PUBKIND_xxx constants below */
+ char pubkind;
} FormData_pg_publication;
/* ----------------
@@ -78,15 +74,11 @@ typedef struct Publication
{
Oid oid;
char *name;
- bool alltables;
bool pubviaroot;
PublicationActions pubactions;
+ char pubkind;
} Publication;
-extern Publication *GetPublication(Oid pubid);
-extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
-extern List *GetRelationPublications(Oid relid);
-
/*---------
* Expected values for pub_partopt parameter of GetRelationPublications(),
* which allows callers to specify which partitions of partitioned tables
@@ -103,16 +95,42 @@ typedef enum PublicationPartOpt
PUBLICATION_PART_ALL,
} PublicationPartOpt;
-extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
-extern List *GetAllTablesPublications(void);
-extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
-
-extern Oid get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid, bool missing_ok);
-
+/* Publication kinds */
+#define PUBKIND_ALLTABLES 'a' /* all tables publication */
+#define PUBKIND_TABLE 't' /* table publication */
+#define PUBKIND_SCHEMA 's' /* schema publication */
+#define PUBKIND_EMPTY 'e' /* empty publication */
+
+/*
+ * Return the publication kind.
+*/
+static inline char
+get_publication_kind(char *strpubkind)
+{
+ if (strcmp(strpubkind, "a") == 0)
+ return PUBKIND_ALLTABLES;
+ else if (strcmp(strpubkind, "t") == 0)
+ return PUBKIND_TABLE;
+ else if (strcmp(strpubkind, "s") == 0)
+ return PUBKIND_SCHEMA;
+
+ return PUBKIND_EMPTY;
+}
+
+/*
+ * Return the publication kind string.
+*/
+static inline char*
+get_publication_kind_string(char *strpubkind)
+{
+ if (strcmp(strpubkind, "a") == 0)
+ return "all tables";
+ else if (strcmp(strpubkind, "t") == 0)
+ return "table";
+ else if (strcmp(strpubkind, "s") == 0)
+ return "schema";
+
+ return "empty";
+}
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..2326a946d8 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -16,13 +16,66 @@
#define PUBLICATIONCMDS_H
#include "catalog/objectaddress.h"
+#include "catalog/pg_publication.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Oid relid);
+
+extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllTablesPublications(void);
+extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaOid,
+ PublicationPartOpt pub_partopt);
+extern bool is_publishable_relation(Relation rel);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
+
+/*
+ * Invalidate the relations.
+ */
+static inline void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point
+ * it's cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+
+ CacheInvalidateRelcacheByRelid(relid);
+ }
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6a4d82f0a8..89c48e3252 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -483,6 +483,7 @@ typedef enum NodeTag
T_CTECycleClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e28248af32..d96aafd626 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,23 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* type of this schemaspec */
+ char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1822,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3649,7 @@ typedef struct CreatePublicationStmt
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
+ List *schemas; /* Optional list of schemas to add */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3642,10 +3661,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
+ /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE/SCHEMA */
List *tables; /* List of tables to add/drop */
+ List *schemas; /* List of schemas to add/drop/set */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..4415d9cd76 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -19,6 +19,7 @@
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
+#include "catalog/objectaddress.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..67f843f465 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 | Inserts | Updates | Deletes | Truncates | Via root | Pub Kind
+--------------------+--------------------------+---------+---------+---------+-----------+----------+----------
+ testpib_ins_trunct | regress_publication_user | t | f | f | f | f | empty
+ testpub_default | regress_publication_user | f | t | f | f | f | empty
(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 | Inserts | Updates | Deletes | Truncates | Via root | Pub Kind
+--------------------+--------------------------+---------+---------+---------+-----------+----------+----------
+ testpib_ins_trunct | regress_publication_user | t | f | f | f | f | empty
+ testpub_default | regress_publication_user | t | t | t | f | f | empty
(2 rows)
--- adding tables
@@ -60,19 +60,19 @@ CREATE TABLE testpub_tbl2 (id serial primary key, data text);
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
-- fail - can't drop from all tables publication
ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
-SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
- pubname | puballtables
-----------------------+--------------
- testpub_foralltables | t
+DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
+SELECT pubname, pubkind FROM pg_publication WHERE pubname = 'testpub_foralltables';
+ pubname | pubkind
+----------------------+---------
+ testpub_foralltables | a
(1 row)
\d+ testpub_tbl2
@@ -88,9 +88,9 @@ Publications:
\dRp+ testpub_foralltables
Publication testpub_foralltables
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | t | t | t | f | f | f
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+------------
+ regress_publication_user | t | t | f | f | f | all tables
(1 row)
DROP TABLE testpub_tbl2;
@@ -102,19 +102,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 | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | table
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 | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | table
Tables:
"public.testpub_tbl3"
@@ -133,10 +133,10 @@ ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (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 | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | table
Tables:
"public.testpub_parted"
@@ -149,10 +149,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 | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | t | table
Tables:
"public.testpub_parted"
@@ -172,10 +172,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 | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | table
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -213,10 +213,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 | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | f | f | table
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -260,10 +260,10 @@ DROP TABLE testpub_parted;
DROP VIEW testpub_view;
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 | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | f | f | table
(1 row)
-- fail - must be owner of publication
@@ -273,20 +273,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 | Inserts | Updates | Deletes | Truncates | Via root | Pub Kind
+-------------+--------------------------+---------+---------+---------+-----------+----------+----------
+ testpub_foo | regress_publication_user | t | t | t | f | f | table
(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 | Inserts | Updates | Deletes | Truncates | Via root | Pub Kind
+-----------------+---------------------------+---------+---------+---------+-----------+----------+----------
+ testpub_default | regress_publication_user2 | t | t | t | f | f | table
(1 row)
DROP PUBLICATION testpub_default;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..3b4f62025f 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -141,6 +141,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..a5391f20b8 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,7 +51,7 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
-SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
+SELECT pubname, pubkind FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..fb5daa49eb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,6 +2047,7 @@ PublicationActions
PublicationInfo
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2332,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v20-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v20-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From 60d087066c71d9170efe532ed248219f9fdd1449 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Fri, 13 Aug 2021 15:27:05 +0530
Subject: [PATCH v20 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 104 ++++-
doc/src/sgml/ref/alter_publication.sgml | 47 ++-
doc/src/sgml/ref/create_publication.sgml | 45 ++-
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 393 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 192 ++++++++-
src/test/subscription/t/001_rep_changes.pl | 150 ++++++-
9 files changed, 942 insertions(+), 28 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..21613dba56 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6109,16 +6114,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
- <row>
- <entry role="catalog_table_entry"><para role="column_definition">
- <structfield>puballtables</structfield> <type>bool</type>
- </para>
- <para>
- If true, this publication automatically includes all tables
- in the database, including any that will be created in the future.
- </para></entry>
- </row>
-
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>pubinsert</structfield> <type>bool</type>
@@ -6169,6 +6164,28 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubkind</structfield> <type>char</type>
+ </para>
+ <para>
+ Publication kind:
+ <literal>a</literal> = <literal>FOR ALL TABLES</literal> publication kind,
+ <literal>t</literal> = <literal>FOR TABLE</literal> publication kind,
+ <literal>s</literal> = <literal>FOR SCHEMA</literal> publication kind,
+ <literal>e</literal> = Empty publication kind.
+ If a publication is created without specifying any of
+ <literal>FOR ALL TABLES</literal>, <literal>FOR TABLE</literal> or
+ <literal>FOR SCHEMA</literal> option, then the publication will be
+ created as an empty publication kind. When a table or schema is added to
+ the publication using <link linkend="sql-alterpublication">
+ <command>ALTER PUBLICATION</command></link> then the publication kind
+ will be changed to <literal>t</literal> or <literal>s</literal>
+ respectively. The publication kind cannot be changed in other cases.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6253,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11354,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR SCHEMA</literal>, so for such publications there will be a
+ row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..94f77d934b 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -63,6 +77,8 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
+ The <literal>ADD SCHEMA</literal> and <literal>SET SCHEMA</literal> to a
+ publication requires the invoking user to be a superuser.
To alter the owner, you must also be a direct or indirect member of the new
owning role. The new owner must have <literal>CREATE</literal> privilege on
the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
@@ -97,6 +113,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +166,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..6b3e4036ad 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ | FOR SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
+ | FOR ALL TABLES
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR SCHEMA</literal> is not specified, then the publication starts
+ out with an empty set of tables. That is useful if tables or schemas are to
+ be added later.
</para>
<para>
@@ -170,9 +182,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ and <command>FOR SCHEMA</command> clause requires the invoking user to be a
+ superuser.
</para>
<para>
@@ -222,6 +235,22 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a4ee54d516..fcdd277b83 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2301,6 +2301,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2337,6 +2346,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..49ea22f427 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 67f843f465..52ccdde0e7 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,48 @@ DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added, dropped or set on FOR TABLE publications.
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added, dropped or set on FOR TABLE publications.
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+ERROR: publication "testpub_fortable" is defined as FOR TABLE
+DETAIL: Schemas cannot be added, dropped or set on FOR TABLE publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added, dropped or set on FOR SCHEMA publications.
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added, dropped or set on FOR SCHEMA publications.
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+ERROR: publication "testpub_forschema" is defined as FOR SCHEMA
+DETAIL: Tables cannot be added, dropped or set on FOR SCHEMA publications.
SELECT pubname, pubkind FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | pubkind
----------------------+---------
@@ -94,7 +136,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -246,18 +288,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +334,355 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | t | t | t | f | f | table
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ Publication testpub3_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schemas cannot be added to publications.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+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 | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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 | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1_renamed"
+ "pub_test2"
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+ Publication testpub2_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+ERROR: schema "pub_test1" is already member of publication "testpub1_forschema"
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+ERROR: schema "pub_test2" is not part of the publication
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP schema pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET schema pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP schema pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD schema pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "public"
+
+-- alter publication set schema should change the publication kind from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | empty
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | Inserts | Updates | Deletes | Truncates | Via root | Pub kind
+--------------------------+---------+---------+---------+-----------+----------+----------
+ regress_publication_user | t | t | t | t | f | schema
+Schemas:
+ "pub_test1"
+
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..56d9b852fd 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index a5391f20b8..27471acb81 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,39 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- fail - can't add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD SCHEMA pub_test;
+-- fail - can't drop schema from table publication
+ALTER PUBLICATION testpub_fortable DROP SCHEMA pub_test;
+-- fail - can't set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't add table to for schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test2.tbl1;
+-- fail - can't drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test1.tbl1;
+-- fail - can't set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test1.tbl1;
+
SELECT pubname, pubkind FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -133,9 +160,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -143,12 +172,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +198,170 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP schema pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET schema pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP schema pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD schema pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema should change the publication kind from e to s
+-- while altering an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 0c84d87873..556af52275 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = PostgresNode->new('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.30.2
On Mon, Aug 9, 2021 at 9:50 PM Mark Dilger <mark.dilger@enterprisedb.com>
wrote:
On Aug 6, 2021, at 1:32 AM, vignesh C <vignesh21@gmail.com> wrote:
the attached v19 patch
With v19 applied, a schema owner can publish the contents of a table
regardless of ownership or permissions on that table:
+CREATE ROLE user1; +GRANT CREATE ON DATABASE regression TO user1; +CREATE ROLE user2; +GRANT CREATE ON DATABASE regression TO user2; +SET SESSION AUTHORIZATION user1; +CREATE SCHEMA user1schema; +GRANT CREATE, USAGE ON SCHEMA user1schema TO user2; +RESET SESSION AUTHORIZATION; +SET SESSION AUTHORIZATION user2; +CREATE TABLE user1schema.user2private (junk text); +REVOKE ALL PRIVILEGES ON user1schema.user2private FROM PUBLIC; +REVOKE ALL PRIVILEGES ON user1schema.user2private FROM user1; +CREATE TABLE user1schema.user2public (junk text); +GRANT SELECT ON user1schema.user2public TO PUBLIC; +RESET SESSION AUTHORIZATION; +SET SESSION AUTHORIZATION user1; +SELECT * FROM user1schema.user2private; +ERROR: permission denied for table user2private +SELECT * FROM user1schema.user2public; + junk +------ +(0 rows) + +CREATE PUBLICATION user1pub; +WARNING: wal_level is insufficient to publish logical changes +HINT: Set wal_level to logical before creating subscriptions. +ALTER PUBLICATION user1pub + ADD TABLE user1schema.user2public; +ERROR: must be owner of table user2public +ALTER PUBLICATION user1pub + ADD TABLE user1schema.user2private, user1schema.user2public; +ERROR: must be owner of table user2private +SELECT * FROM pg_catalog.pg_publication_tables + WHERE pubname = 'user1pub'; + pubname | schemaname | tablename +---------+------------+----------- +(0 rows) + +ALTER PUBLICATION user1pub ADD SCHEMA user1schema; +SELECT * FROM pg_catalog.pg_publication_tables + WHERE pubname = 'user1pub'; + pubname | schemaname | tablename +----------+-------------+-------------- + user1pub | user1schema | user2private + user1pub | user1schema | user2public +(2 rows)It is a bit counterintuitive that schema owners do not have
administrative privileges over tables within their schemas, but that's how
it is. The design of this patch seems to assume otherwise. Perhaps ALTER
PUBLICATION ... ADD SCHEMA should be restricted to superusers, just as FOR
ALL TABLES?
Thanks for the comment, this is handled in the v20 patch attached at [1]/messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com.
Alternatively, you could add ownership checks per table to mirror the
behavior of ALTER PUBLICATION ... ADD TABLE, but that would foreclose the
option of automatically updating the list of tables in the publication as
new tables are added to the schema, since those new tables would not
necessarily belong to the schema owner, and having a error thrown during
CREATE TABLE would be quite unfriendly. I think until this is hammered
out, it is safer to require superuser privileges and then we can revisit
this issue and loosen the requirement in a subsequent commit.
I agree with Amit on this point for handling this as a separate patch, I
have not made any change for this, kept the behavior as is.
[1]: /messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com
/messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com
Regards,
Vignesh
On Tue, Aug 10, 2021 at 1:40 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Aug 6, 2021 at 6:32 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v19 patch has the fixes for the
comments.
Some more review comments, this time for the v19 patch:
(1) In patch v19-0002, there's still a couple of instances where it
says "publication type" instead of "publication kind".
Modified
(2) src/backend/catalog/pg_publication.c
"This should only be used for normal publications."
What exactly is meant by that - what type is considered normal? Maybe
that comment should be more specific.
Modified
(3) src/backend/catalog/pg_publication.c
GetSchemaPublicationsFunction header says "Gets list of publication oids for publications
marked as FOR SCHEMA."Shouldn't it say something like: "Gets the list of FOR SCHEMA
publication oids associated with a specified schema oid." or something
like that?
(since the function accepts a schemaid parameter)
Modfified
(4) src/backend/commands/publicationcmds.c
In AlterPublicationSchemas(), I notice that the two error cases
"cannot be added to or dropped ..." don't check stmt->action for
DEFELEM_ADD/DEFELEM_DROP.
Is that OK? (i.e. should these cases error out if stmt->action is not
DEFELEM_ADD/DEFELEM_DROP?)
Also, I assume that the else part (not DEFELEM_ADD/DEFELEM_DROP) is
the "Set" case? Maybe a comment should be added to the top of the else
part.
The error message should also include set, I have modified the error
message accordingly.
(5) src/backend/commands/publicationcmds.c
Typo (same in 2 places): "automaically" -> "automatically"+ * will be released automaically at the end of create publication
See functions:
(i) CreatePublication
(ii) AlterPublicationSchemas
Modified.
(6) src/backend/commands/publicationcmds.c
LockSchemaListFunction header says "are locked in ShareUpdateExclusiveLock mode" but
then code calls LockDatabaseObject using "AccessShareLock".
Modified.
Thanks for the comments, these issues are fixed in the v20 patch attached
at [1]/messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com.
[1]: /messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com
/messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com
Regards,
Vignesh
On Thu, Aug 12, 2021 at 5:54 PM Masahiko Sawada <sawada.mshk@gmail.com>
wrote:
On Fri, Aug 6, 2021 at 5:33 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v19 patch has the fixes for the
comments.
Thank you for updating the patch!
Here are some comments on v19 patch:
+ case OCLASS_PUBLICATION_SCHEMA: + RemovePublicationSchemaById(object->objectId); + break;+void +RemovePublicationSchemaById(Oid psoid) +{ + Relation rel; + HeapTuple tup; + + rel = table_open(PublicationSchemaRelationId, RowExclusiveLock); + + tup = SearchSysCache1(PUBLICATIONSCHEMA,
ObjectIdGetDatum(psoid));
+ + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for publication schema
%u",
+ psoid); + + CatalogTupleDelete(rel, &tup->t_self); + + ReleaseSysCache(tup); + + table_close(rel, RowExclusiveLock); +}Since RemovePublicationSchemaById() does simple catalog tuple
deletion, it seems to me that we can DropObjectById() to delete the
row of pg_publication_schema.
Relation cache invalidations were missing in the function, I have added and
retained the function with invalidation changes.
--- { - ScanKeyInit(&key[0], + ScanKeyData skey[1]; + + ScanKeyInit(&skey[0], Anum_pg_class_relkind, BTEqualStrategyNumber, F_CHAREQ,CharGetDatum(RELKIND_PARTITIONED_TABLE));
- scan = table_beginscan_catalog(classRel, 1, key); + scan = table_beginscan_catalog(classRel, 1, skey);Since we specify 1 as the number of keys in table_beginscan_catalog(),
can we reuse 'key' instead of using 'skey'?
Modified.
---
Even if we drop all tables added to the publication from it, 'pubkind'
doesn't go back to 'empty'. Is that intentional behavior? If we do
that, we can save the lookup of pg_publication_rel and
pg_publication_schema in some cases, and we can switch the publication
that was created as FOR SCHEMA to FOR TABLE and vice versa.
I felt this can be handled as a separate patch as the same scenario applies
for all tables publication too. Thoughts?
--- +static void +UpdatePublicationKindTupleValue(Relation rel, HeapTuple tup, int col, + char
pubkind)
Since all callers of this function specify Anum_pg_publication_pubkind
to 'col', it seems 'col' is not necessary.
Modified
--- +static void +AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, + HeapTuple tup, Form_pg_publication pubform)I think we don't need to pass 'pubform' to this function since we can
get it by GETSTRUCT(tup).
Modified.
--- + Oid schemaId =
get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+ List *schemaPubids =
GetSchemaPublications(schemaId);
Can we defer to get the list of schema publications (i.g.,
'schemaPubids') until we find the PUBKIND_SCHEMA publication? Perhaps
the same is true for building 'pubids'.
I felt that we can get the publication information and use it whenever
required instead of querying in the loop. Thoughts?
--- + List of publications + Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root | PubKind
+--------------------+--------------------------+------------+---------+---------+---------+-----------+----------+---------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | e + testpub_default | regress_publication_user | f | f | t | f | f | f | eI think it's more readable if \dRp shows 'all tables', 'table',
'schema', and 'empty' in PubKind instead of the single character.
Modified
I think 'Pub kind' is more consistent with other column names.
Modified
Thanks for the comments, these issues are fixed in the v20 patch attached
at [1]/messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com.
[1]: /messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com
/messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com
Regards,
Vignesh
On Mon, Aug 9, 2021 at 10:23 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Sun, Aug 8, 2021 at 2:52 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Aug 6, 2021 at 4:39 PM Amit Kapila <amit.kapila16@gmail.com>
wrote:
On Fri, Aug 6, 2021 at 2:02 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 4, 2021 at 4:10 PM Amit Kapila <amit.kapila16@gmail.com>
wrote:
On Tue, Aug 3, 2021 at 8:38 PM vignesh C <vignesh21@gmail.com>
wrote:
6. + {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */ + PublicationSchemaPsnspcidPspubidIndexId, + 2, + { + Anum_pg_publication_schema_psnspcid, + Anum_pg_publication_schema_pspubid, + 0, + 0 + },Why don't we keep pubid as the first column in this index?
I wanted to keep it similar to PUBLICATIONRELMAP, should we keep it
as
it is, thoughts?
Okay, I see your point. I think for PUBLICATIONRELMAP, we need it
because it is searched using the only relid in
GetRelationPublications, so, similarly, in the patch, you are using
schema_oid in GetSchemaPublications, so probably that will help. I was
wondering why you haven't directly used the cache in
GetSchemaPublications similar to GetRelationPublications?Both of the approaches work, I was not sure which one is better, If
the approach in GetRelationPublications is better, I will change it to
something similar to GetRelationPublications. Thoughts?I think it is better to use the cache as if we don't find the entry in
the cache, then we will anyway search the required entry via sys table
scan, see SearchCatCacheList.
Modified. This is handled in the v20 patch posted at [1]/messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com.
I think the point I wanted to ensure was
that a concurrent session won't blow up the entry while we are looking
at it. How is that ensured?
The concurrency points occur at two places, Walsender session and user
session:
For Walsender process when we query the data from the cache we will get the
results based on historic snapshot. I also debugged and verified that we
get the results based on historic snapshot, if we check the cache during
our operation (insert is just before drop) we will be able to get the
dropped record from the cache as this drop occurred after our insert. And
if we query the cache after the drop, we will not get the dropped
information from the cache. So I feel our existing code is good enough
which handles the concurrency through the historic snapshot.
For user sessions, user session checks for replica identity for
update/delete operations. To prevent concurrency issues, when schema is
added to the publication, the rel cache invalidation happens in
publication_add_schema by calling InvalidatePublicationRels, similarly when
a schema is dropped from the publication, the rel cache invalidation is
handled in RemovePublicationSchemaById by calling
InvalidatePublicationRels. Once the invalidation happens it will check the
system tables again before deciding. I felt this rel cache invalidation
will prevent concurrency issues.
[1]: /messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com
/messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com
Regards,
Vignesh
On Fri, Aug 6, 2021 at 4:02 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 6, 2021 at 2:16 PM vignesh C <vignesh21@gmail.com> wrote:
On Thu, Aug 5, 2021 at 3:54 PM Amit Kapila <amit.kapila16@gmail.com>
wrote:
Few more comments:
===================
1. Do we need the previous column 'puballtables' after adding pubtype
or pubkind in pg_publication?
Removed puballtables, this is handled as part of v20 patch attached at [1]/messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com.
[1]: /messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com
/messages/by-id/CALDaNm00X9SQBokUTy1OxN1Sa2DFsK8rg8j_wLgc-7ZuKcuh0Q@mail.gmail.com
Regards,
Vignesh
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
I think the strict separation between publication-for-tables vs.
publication-for-schemas is a mistake. Why can't I have a publication
that publishes tables t1, t2, t3, *and* schemas s1, s2, s3. Also note
that we have a pending patch to add sequences support to logical
replication. So eventually, a publication will be able to contain a
bunch of different objects of different kinds.
This seems like it's going to create a mess, because the meaning of
"include schema S" will change over time as we add more features.
That is, with the present patch (I suppose, haven't read it) we have
"schema S" meaning "publish all tables in schema S". When that other
patch lands, presumably that same publication definition would start
meaning "publish all tables and sequences in schema S". And a few years
down the road it might start meaning something else again. That sounds
like the sort of potentially app-breaking change that we don't like
to make.
We could avoid that bug-in-waiting if the syntax were more like
"FOR ALL TABLES IN SCHEMA s", which could later extend to
"FOR ALL SEQUENCES IN SCHEMA s", etc. This is then a very clean
intermediate step between publishing one table and "FOR ALL TABLES"
without a schema limitation.
I tend to agree that a single publication should be able to incorporate
any of these options.
regards, tom lane
On Fri, Aug 13, 2021 at 11:59 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Aug 12, 2021 at 5:54 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Fri, Aug 6, 2021 at 5:33 PM vignesh C <vignesh21@gmail.com> wrote:
---
Even if we drop all tables added to the publication from it, 'pubkind'
doesn't go back to 'empty'. Is that intentional behavior? If we do
that, we can save the lookup of pg_publication_rel and
pg_publication_schema in some cases, and we can switch the publication
that was created as FOR SCHEMA to FOR TABLE and vice versa.Do we really want to allow users to change a publication that is FOR
SCHEMA to FOR TABLE? I see that we don't allow to do that FOR TABLES.
postgres=# Alter Publication pub add table tbl1;
ERROR: publication "pub" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
When it comes to FOR ALL TABLES, we can neither add/drop tables
to/from the publication. So it makes sense to me that it never changes
to 'empty'. I'm not sure there are use cases in practice where to
change FOR SCHEMA to FOR TABLE. But it seems a bit weird to me that
pubkind doesn't change to "empty" even if the publication actually
gets empty. It might be just that "empty" is misleading, though.
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Sun, Aug 15, 2021 at 12:23 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
I think the strict separation between publication-for-tables vs.
publication-for-schemas is a mistake. Why can't I have a publication
that publishes tables t1, t2, t3, *and* schemas s1, s2, s3. Also note
that we have a pending patch to add sequences support to logical
replication. So eventually, a publication will be able to contain a
bunch of different objects of different kinds.This seems like it's going to create a mess, because the meaning of
"include schema S" will change over time as we add more features.
That is, with the present patch (I suppose, haven't read it) we have
"schema S" meaning "publish all tables in schema S". When that other
patch lands, presumably that same publication definition would start
meaning "publish all tables and sequences in schema S". And a few years
down the road it might start meaning something else again. That sounds
like the sort of potentially app-breaking change that we don't like
to make.We could avoid that bug-in-waiting if the syntax were more like
"FOR ALL TABLES IN SCHEMA s", which could later extend to
"FOR ALL SEQUENCES IN SCHEMA s", etc. This is then a very clean
intermediate step between publishing one table and "FOR ALL TABLES"
without a schema limitation.
+1
I tend to agree that a single publication should be able to incorporate
any of these options.
I personally prefer that a single publication can include both all
tables and all sequences in a database or a schema. It would be a more
convenient way to specify replicating all objects (tables and
sequences) in a database or a schema.
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Sun, Aug 15, 2021 at 1:23 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
I think the strict separation between publication-for-tables vs.
publication-for-schemas is a mistake. Why can't I have a publication
that publishes tables t1, t2, t3, *and* schemas s1, s2, s3. Also note
that we have a pending patch to add sequences support to logical
replication. So eventually, a publication will be able to contain a
bunch of different objects of different kinds.This seems like it's going to create a mess, because the meaning of
"include schema S" will change over time as we add more features.
That is, with the present patch (I suppose, haven't read it) we have
"schema S" meaning "publish all tables in schema S". When that other
patch lands, presumably that same publication definition would start
meaning "publish all tables and sequences in schema S". And a few years
down the road it might start meaning something else again. That sounds
like the sort of potentially app-breaking change that we don't like
to make.We could avoid that bug-in-waiting if the syntax were more like
"FOR ALL TABLES IN SCHEMA s", which could later extend to
"FOR ALL SEQUENCES IN SCHEMA s", etc. This is then a very clean
intermediate step between publishing one table and "FOR ALL TABLES"
without a schema limitation.I tend to agree that a single publication should be able to incorporate
any of these options.
How about if the improved syntax from Tom Lane [1]/messages/by-id/155565.1628954580@sss.pgh.pa.us also allowed an
"AND" keyword for combining whatever you wish?
Then the question from Peter E. [2]/messages/by-id/4fb39707-dca9-1563-4482-b7a8315c36ca@enterprisedb.com "Why can't I have a publication
that publishes tables t1, t2, t3, *and* schemas s1, s2, s3." would
have an intuitive solution like:
CREATE PUBLICATION pub1
FOR TABLE t1,t2,t3 AND
FOR ALL TABLES IN SCHEMA s1,s2,s3;
------
[1]: /messages/by-id/155565.1628954580@sss.pgh.pa.us
[2]: /messages/by-id/4fb39707-dca9-1563-4482-b7a8315c36ca@enterprisedb.com
Kind Regards,
Peter Smith.
Fujitsu Australia
Peter Smith <smithpb2250@gmail.com> writes:
Then the question from Peter E. [2] "Why can't I have a publication
that publishes tables t1, t2, t3, *and* schemas s1, s2, s3." would
have an intuitive solution like:
CREATE PUBLICATION pub1
FOR TABLE t1,t2,t3 AND
FOR ALL TABLES IN SCHEMA s1,s2,s3;
That seems a bit awkward, since the existing precedent is
to use commas. We shouldn't need more than one FOR noise-word,
either. So I was imagining syntax more like, say,
CREATE PUBLICATION pub1 FOR
TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2,
SEQUENCE seq1,seq2, ALL SEQUENCES IN SCHEMA s3,s4;
Abstractly it'd be
createpub := CREATE PUBLICATION pubname FOR cpitem [, ... ] [ WITH ... ]
cpitem := ALL TABLES |
TABLE name |
ALL TABLES IN SCHEMA name |
ALL SEQUENCES |
SEQUENCE name |
ALL SEQUENCES IN SCHEMA name |
name
The grammar output would need some post-analysis to attribute the
right type to bare "name" items, but that doesn't seem difficult.
regards, tom lane
On Mon, Aug 16, 2021 at 11:31 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Smith <smithpb2250@gmail.com> writes:
Then the question from Peter E. [2] "Why can't I have a publication
that publishes tables t1, t2, t3, *and* schemas s1, s2, s3." would
have an intuitive solution like:CREATE PUBLICATION pub1
FOR TABLE t1,t2,t3 AND
FOR ALL TABLES IN SCHEMA s1,s2,s3;That seems a bit awkward, since the existing precedent is
to use commas. We shouldn't need more than one FOR noise-word,
either. So I was imagining syntax more like, say,
When I wrote that "AND" suggestion I had in mind that commas may get
weird if there were objects with keyword names. e.g. if there was a
schema called SEQUENCE and a SEQUENCE called SEQUENCE then this will
be allowed.
CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA SEQUENCE, SEQUENCE SEQUENCE;
But probably I was just overthinking it.
CREATE PUBLICATION pub1 FOR
TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2,
SEQUENCE seq1,seq2, ALL SEQUENCES IN SCHEMA s3,s4;Abstractly it'd be
createpub := CREATE PUBLICATION pubname FOR cpitem [, ... ] [ WITH ... ]
cpitem := ALL TABLES |
TABLE name |
ALL TABLES IN SCHEMA name |
ALL SEQUENCES |
SEQUENCE name |
ALL SEQUENCES IN SCHEMA name |
nameThe grammar output would need some post-analysis to attribute the
right type to bare "name" items, but that doesn't seem difficult.
That last bare "name" cpitem. looks like it would permit the following syntax:
CREATE PUBLICATION pub FOR a,b,c;
Was that intentional?
------
Kind Regards,
Peter Smith.
Fujitsu Australia.
On Tue, Aug 17, 2021 at 6:40 AM Peter Smith <smithpb2250@gmail.com> wrote:
On Mon, Aug 16, 2021 at 11:31 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
CREATE PUBLICATION pub1 FOR
TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2,
SEQUENCE seq1,seq2, ALL SEQUENCES IN SCHEMA s3,s4;Abstractly it'd be
createpub := CREATE PUBLICATION pubname FOR cpitem [, ... ] [ WITH ... ]
cpitem := ALL TABLES |
TABLE name |
ALL TABLES IN SCHEMA name |
ALL SEQUENCES |
SEQUENCE name |
ALL SEQUENCES IN SCHEMA name |
nameThe grammar output would need some post-analysis to attribute the
right type to bare "name" items, but that doesn't seem difficult.That last bare "name" cpitem. looks like it would permit the following syntax:
CREATE PUBLICATION pub FOR a,b,c;
Was that intentional?
I think so. IIUC, the idea is that after parsing we find out whether
the given name is table, sequence, or any other object. Here, I think
the name could be either of table or sequence because, for schema, we
won't be knowing whether to include tables, sequences, or both in the
schema. Also, we can have the same name for schema and table so it
might be tricky to distinguish among those unless we give priority to
one of those.
--
With Regards,
Amit Kapila.
Amit Kapila <amit.kapila16@gmail.com> writes:
On Tue, Aug 17, 2021 at 6:40 AM Peter Smith <smithpb2250@gmail.com> wrote:
On Mon, Aug 16, 2021 at 11:31 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Abstractly it'd be
createpub := CREATE PUBLICATION pubname FOR cpitem [, ... ] [ WITH ... ]
cpitem := ALL TABLES |
TABLE name |
ALL TABLES IN SCHEMA name |
ALL SEQUENCES |
SEQUENCE name |
ALL SEQUENCES IN SCHEMA name |
nameThe grammar output would need some post-analysis to attribute the
right type to bare "name" items, but that doesn't seem difficult.
That last bare "name" cpitem. looks like it would permit the following syntax:
CREATE PUBLICATION pub FOR a,b,c;
Was that intentional?
I think so.
I had supposed that we could throw an error at the post-processing stage,
or alternatively default to assuming that such names are tables.
Now you could instead make the grammar work like
cpitem := ALL TABLES |
TABLE name [, ...] |
ALL TABLES IN SCHEMA name [, ...] |
ALL SEQUENCES |
SEQUENCE name [, ...] |
ALL SEQUENCES IN SCHEMA name [, ...]
which would result in a two-level-list data structure. I'm not sure
that this is better, as any sort of mistake would result in a very
uninformative generic "syntax error" from Bison. Errors out of a
post-processing stage could be more specific than that.
(Perhaps, though, we should *document* it like the latter way,
even if the actual implementation is more like the first way.)
regards, tom lane
On Tue, Aug 17, 2021 at 6:55 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Kapila <amit.kapila16@gmail.com> writes:
On Tue, Aug 17, 2021 at 6:40 AM Peter Smith <smithpb2250@gmail.com>
wrote:
On Mon, Aug 16, 2021 at 11:31 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Abstractly it'd be
createpub := CREATE PUBLICATION pubname FOR cpitem [, ... ] [ WITH
... ]
cpitem := ALL TABLES |
TABLE name |
ALL TABLES IN SCHEMA name |
ALL SEQUENCES |
SEQUENCE name |
ALL SEQUENCES IN SCHEMA name |
nameThe grammar output would need some post-analysis to attribute the
right type to bare "name" items, but that doesn't seem difficult.That last bare "name" cpitem. looks like it would permit the following
syntax:
CREATE PUBLICATION pub FOR a,b,c;
Was that intentional?I think so.
I had supposed that we could throw an error at the post-processing stage,
or alternatively default to assuming that such names are tables.Now you could instead make the grammar work like
cpitem := ALL TABLES |
TABLE name [, ...] |
ALL TABLES IN SCHEMA name [, ...] |
ALL SEQUENCES |
SEQUENCE name [, ...] |
ALL SEQUENCES IN SCHEMA name [, ...]which would result in a two-level-list data structure. I'm not sure
that this is better, as any sort of mistake would result in a very
uninformative generic "syntax error" from Bison. Errors out of a
post-processing stage could be more specific than that.
I preferred the implementation in the lines Tom Lane had proposed at [1]/messages/by-id/877603.1629120678@sss.pgh.pa.us.
Is it ok if the implementation is something like below:
CreatePublicationStmt:
CREATE PUBLICATION name FOR pub_obj_list opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
n->options = $6;
n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
| pub_obj_list ',' PublicationObjSpec
{ $$ = lappend($1, $3); }
;
/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
PublicationObjSpec: TABLE pubobj_expr
{ ....}
| ALL TABLES IN_P SCHEMA pubobj_expr
{ ....}
| pubobj_expr
{ ....}
;
pubobj_expr:
any_name
{ ....}
| any_name '*'
{ ....}
| ONLY any_name
{ ....}
| ONLY '(' any_name ')'
{ ....}
| CURRENT_SCHEMA
{ ....}
;
I needed pubobj_expr to support the existing syntaxes supported by
relation_expr and also to handle CURRENT_SCHEMA support in case of the "FOR
ALL TABLES IN SCHEMA" feature. I changed the name to any_name to support
objects like "sch1.t1".
I felt if a user specified "FOR ALL TABLES", the user should not be allowed
to combine it with "FOR TABLE" and "FOR ALL TABLES IN SCHEMA" as "FOR ALL
TABLES" anyway will include all the tables.
Should we support the similar syntax in case of alter publication, like
"ALTER PUBLICATION pub1 ADD TABLE t1,t2, ALL TABLES IN SCHEMA sch1, sch2"
or shall we keep these separate like "ALTER PUBLICATION pub1 ADD TABLE t1,
t2" and "ALTER PUBLICATION pub1 ADD ALL TABLES IN SCHEMA sch1, sch2". I
preferred to keep it separate as we have kept ADD/DROP separately which
cannot be combined currently. I have not mentioned SEQUENCE handling
separately, the sequence will be extended in similar lines. I thought of
throwing an error at post processing, the first option that Tom Lane had
suggested if the user specified syntax like: create publication pub1 for
t1,t2;
Thoughts?
[1]: /messages/by-id/877603.1629120678@sss.pgh.pa.us
/messages/by-id/877603.1629120678@sss.pgh.pa.us
Regards,
Vignesh
On Mon, Aug 23, 2021 at 11:16 PM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Aug 17, 2021 at 6:55 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Kapila <amit.kapila16@gmail.com> writes:
On Tue, Aug 17, 2021 at 6:40 AM Peter Smith <smithpb2250@gmail.com> wrote:
On Mon, Aug 16, 2021 at 11:31 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Abstractly it'd be
createpub := CREATE PUBLICATION pubname FOR cpitem [, ... ] [ WITH ... ]
cpitem := ALL TABLES |
TABLE name |
ALL TABLES IN SCHEMA name |
ALL SEQUENCES |
SEQUENCE name |
ALL SEQUENCES IN SCHEMA name |
nameThe grammar output would need some post-analysis to attribute the
right type to bare "name" items, but that doesn't seem difficult.That last bare "name" cpitem. looks like it would permit the following syntax:
CREATE PUBLICATION pub FOR a,b,c;
Was that intentional?I think so.
I had supposed that we could throw an error at the post-processing stage,
or alternatively default to assuming that such names are tables.Now you could instead make the grammar work like
cpitem := ALL TABLES |
TABLE name [, ...] |
ALL TABLES IN SCHEMA name [, ...] |
ALL SEQUENCES |
SEQUENCE name [, ...] |
ALL SEQUENCES IN SCHEMA name [, ...]which would result in a two-level-list data structure. I'm not sure
that this is better, as any sort of mistake would result in a very
uninformative generic "syntax error" from Bison. Errors out of a
post-processing stage could be more specific than that.I preferred the implementation in the lines Tom Lane had proposed at [1]. Is it ok if the implementation is something like below:
CreatePublicationStmt:
CREATE PUBLICATION name FOR pub_obj_list opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
n->options = $6;
n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
| pub_obj_list ',' PublicationObjSpec
{ $$ = lappend($1, $3); }
;
/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
PublicationObjSpec: TABLE pubobj_expr
{ ....}
| ALL TABLES IN_P SCHEMA pubobj_expr
{ ....}
| pubobj_expr
{ ....}
;
pubobj_expr:
any_name
{ ....}
| any_name '*'
{ ....}
| ONLY any_name
{ ....}
| ONLY '(' any_name ')'
{ ....}
| CURRENT_SCHEMA
{ ....}
;
"FOR ALL TABLES” (that includes all tables in the database) is missing
in this syntax?
I needed pubobj_expr to support the existing syntaxes supported by relation_expr and also to handle CURRENT_SCHEMA support in case of the "FOR ALL TABLES IN SCHEMA" feature. I changed the name to any_name to support objects like "sch1.t1".
I think that relation_expr also accepts objects like "sch1.t1", no?
I felt if a user specified "FOR ALL TABLES", the user should not be allowed to combine it with "FOR TABLE" and "FOR ALL TABLES IN SCHEMA" as "FOR ALL TABLES" anyway will include all the tables.
I think so too.
Should we support the similar syntax in case of alter publication, like "ALTER PUBLICATION pub1 ADD TABLE t1,t2, ALL TABLES IN SCHEMA sch1, sch2" or shall we keep these separate like "ALTER PUBLICATION pub1 ADD TABLE t1, t2" and "ALTER PUBLICATION pub1 ADD ALL TABLES IN SCHEMA sch1, sch2". I preferred to keep it separate as we have kept ADD/DROP separately which cannot be combined currently.
If we support the former syntax, the latter two syntaxes are also
supported. Why do we want to support only the latter separate two
syntaxes?
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Wed, Aug 25, 2021 at 1:19 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Mon, Aug 23, 2021 at 11:16 PM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Aug 17, 2021 at 6:55 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Kapila <amit.kapila16@gmail.com> writes:
On Tue, Aug 17, 2021 at 6:40 AM Peter Smith <smithpb2250@gmail.com> wrote:
On Mon, Aug 16, 2021 at 11:31 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Abstractly it'd be
createpub := CREATE PUBLICATION pubname FOR cpitem [, ... ] [ WITH ... ]
cpitem := ALL TABLES |
TABLE name |
ALL TABLES IN SCHEMA name |
ALL SEQUENCES |
SEQUENCE name |
ALL SEQUENCES IN SCHEMA name |
nameThe grammar output would need some post-analysis to attribute the
right type to bare "name" items, but that doesn't seem difficult.That last bare "name" cpitem. looks like it would permit the following syntax:
CREATE PUBLICATION pub FOR a,b,c;
Was that intentional?I think so.
I had supposed that we could throw an error at the post-processing stage,
or alternatively default to assuming that such names are tables.Now you could instead make the grammar work like
cpitem := ALL TABLES |
TABLE name [, ...] |
ALL TABLES IN SCHEMA name [, ...] |
ALL SEQUENCES |
SEQUENCE name [, ...] |
ALL SEQUENCES IN SCHEMA name [, ...]which would result in a two-level-list data structure. I'm not sure
that this is better, as any sort of mistake would result in a very
uninformative generic "syntax error" from Bison. Errors out of a
post-processing stage could be more specific than that.I preferred the implementation in the lines Tom Lane had proposed at [1]. Is it ok if the implementation is something like below:
CreatePublicationStmt:
CREATE PUBLICATION name FOR pub_obj_list opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
n->options = $6;
n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
| pub_obj_list ',' PublicationObjSpec
{ $$ = lappend($1, $3); }
;
/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
PublicationObjSpec: TABLE pubobj_expr
{ ....}
| ALL TABLES IN_P SCHEMA pubobj_expr
{ ....}
| pubobj_expr
{ ....}
;
pubobj_expr:
any_name
{ ....}
| any_name '*'
{ ....}
| ONLY any_name
{ ....}
| ONLY '(' any_name ')'
{ ....}
| CURRENT_SCHEMA
{ ....}
;"FOR ALL TABLES” (that includes all tables in the database) is missing
in this syntax?
"FOR ALL TABLES" is present in CreatePublicationStmt rule, sorry for
not including all of CreatePublicationStmt rule in the previous mail,
I thought of keeping the contents shorter:
CreatePublicationStmt:
CREATE PUBLICATION name opt_definition
{....}
| CREATE PUBLICATION name FOR ALL TABLES opt_definition
{....}
| CREATE PUBLICATION name FOR pub_obj_list opt_definition
{....}
;
It is not in pub_obj_list as the user will be able to specify either
of "FOR ALL TABLES" or "FOR TABLE/ FOR ALL TABLES IN SCHEMA" along
with create publication.
I needed pubobj_expr to support the existing syntaxes supported by relation_expr and also to handle CURRENT_SCHEMA support in case of the "FOR ALL TABLES IN SCHEMA" feature. I changed the name to any_name to support objects like "sch1.t1".
I think that relation_expr also accepts objects like "sch1.t1", no?
Earlier syntax only supported relations, the relations were parsed
into RangeVar datatype. The new feature supports schema for which only
the schema name is required. To keep the parsing rule common, I used
any_name which will store the dotted name into a list for both
relation and schema. I will later convert it into rangevar for
relation and schema oid for schema names during the processing and
create the publication. I felt relation_expr was able to handle dotted
names because of qualified_name having "ColId indirection", here
indirection rule takes care of handling the dotted names.
I felt if a user specified "FOR ALL TABLES", the user should not be allowed to combine it with "FOR TABLE" and "FOR ALL TABLES IN SCHEMA" as "FOR ALL TABLES" anyway will include all the tables.
I think so too.
Should we support the similar syntax in case of alter publication, like "ALTER PUBLICATION pub1 ADD TABLE t1,t2, ALL TABLES IN SCHEMA sch1, sch2" or shall we keep these separate like "ALTER PUBLICATION pub1 ADD TABLE t1, t2" and "ALTER PUBLICATION pub1 ADD ALL TABLES IN SCHEMA sch1, sch2". I preferred to keep it separate as we have kept ADD/DROP separately which cannot be combined currently.
If we support the former syntax, the latter two syntaxes are also
supported. Why do we want to support only the latter separate two
syntaxes?
We can support either syntax, I was not sure which one is better. If
alter also should support similar syntax I can do it as a separate
patch so as to not increase the main patch size. Thoughts?
Attached v21 patch has the changes based on the new syntax and fixes
few of the other review comments provided by reviewers.
Regards,
Vignesh
Attachments:
v21-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v21-0001-Added-schema-level-support-for-publication.patchDownload
From 5d33e21218bac24d5c8699190e3971109be5b9a9 Mon Sep 17 00:00:00 2001
From: Vigneshwaran c <vignesh21@gmail.com>
Date: Mon, 26 Jul 2021 09:25:22 +0530
Subject: [PATCH v21 1/2] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 141 +++++++
src/backend/catalog/pg_publication.c | 331 ++++++++++++++--
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 401 ++++++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 2 +-
src/backend/nodes/equalfuncs.c | 2 +-
src/backend/parser/gram.y | 193 ++++++++--
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 4 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++-
src/bin/pg_dump/pg_dump.h | 15 +
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 189 +++++++--
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 8 +
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 30 ++
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 49 ++-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 6 +-
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 7 +
34 files changed, 1530 insertions(+), 147 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..07abcbae92 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -829,6 +830,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1125,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1946,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2259,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2352,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2847,6 +2901,49 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ *pubname = get_publication_name(psform->pspubid, false);
+ *nspname = get_namespace_name(psform->psnspcid);
+ if (!(*nspname))
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3902,6 +3999,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4589,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5828,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..3bd97c3207 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -214,6 +216,96 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schemas cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schemas cannot be added to publications.")));
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ schemaRels = GetSchemaPublicationRelations(schemaoid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -238,10 +330,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,36 +399,79 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
+ systable_endscan(scan);
+ table_close(pubrelsrel, AccessShareLock);
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
+ return result;
+}
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
}
systable_endscan(scan);
- table_close(pubrelsrel, AccessShareLock);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of FOR SCHEMA publication oids associated with a specified
+ * schema oid
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONSCHEMAMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_schema) GETSTRUCT(tup))->pspubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
return result;
}
@@ -342,7 +514,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -404,6 +576,96 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets list of relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaOid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(schemaOid != InvalidOid);
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ /* Skip the relations which are not publishable */
+ if (!is_publishable_class(relForm->oid, relForm))
+ continue;
+
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaOid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -533,10 +795,25 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ if (!relids || !schemarelids)
+ tables = list_concat(relids, schemarelids);
+ else
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..35f47d3253 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..9257bf275a 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -34,25 +36,26 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +141,142 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ConvertPubObjSpecListToOidList(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("for table/for all tables in schema should be specified before the object"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ RangeVar *rel;
+ char *relname = strVal(linitial(pubobj->name));
+
+ if (list_length(pubobj->name) == 1 &&
+ (strcmp(relname, "CURRENT_SCHEMA") == 0))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid relation name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ rel = makeRangeVarFromNameList(pubobj->name);
+ rel->inh = pubobj->inh;
+ rel->location = pubobj->location;
+ rel->relpersistence = RELPERSISTENCE_PERMANENT;
+ *rels = lappend(*rels, rel);
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_SCHEMA)
+ {
+ Oid schemaoid;
+ char *schemaname;
+
+ if (list_length(pubobj->name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(pubobj->name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ if (pubobj->spl_rel_type_syn)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(linitial(pubobj->name));
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+ char *nspname;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaoid);
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -155,6 +294,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaoidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -168,6 +309,12 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create FOR ALL TABLES publication")));
+ /* FOR SCHEMA requires superuser */
+ if (stmt->schemas && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR SCHEMA publication")));
+
rel = table_open(PublicationRelationId, RowExclusiveLock);
/* Check if name is used */
@@ -224,19 +371,29 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ConvertPubObjSpecListToOidList(stmt->pubobjects, pstate, &relations,
+ &schemaoidlist);
+ if (relations != NIL)
{
- List *rels;
+ List *rels = OpenTableList(relations);
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- table_close(rel, RowExclusiveLock);
+ if (schemaoidlist != NIL)
+ {
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of create
+ * publication command.
+ */
+ LockSchemaList(schemaoidlist);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ }
+ table_close(rel, RowExclusiveLock);
InvokeObjectPostCreateHook(PublicationRelationId, puboid, 0);
if (wal_level != WAL_LEVEL_LOGICAL)
@@ -316,31 +473,21 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
-
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat(relids, schemarelids);
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
@@ -367,15 +514,17 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ errdetail("Tables cannot be added, dropped or set on FOR ALL TABLES publications.")));
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
- if (stmt->tableAction == DEFELEM_ADD)
+ if (stmt->action == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -426,11 +575,72 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
+{
+ List *schemaoidlist = NIL;
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added, dropped or set on FOR ALL TABLES publications.")));
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /* Convert the text list into oid list */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaoidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaoidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +669,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +709,58 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_schema pubsch;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ schemaRels = GetSchemaPublicationRelations(pubsch->psnspcid,
+ PUBLICATION_PART_ALL);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaoid, 0, AccessShareLock);
+ }
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -607,7 +871,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
{
ListCell *lc;
- Assert(!stmt || !stmt->for_all_tables);
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->schemas));
foreach(lc, rels)
{
@@ -631,6 +895,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || (!stmt->for_all_tables && !stmt->tables));
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +962,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..3e57a152f4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12253,6 +12253,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..b0d19cabc3 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4823,7 +4823,7 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(tables);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..f4f7e896dd 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2309,7 +2309,7 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(tables);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 39a2849eba..e408acfce3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +429,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,7 +556,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
-
+%type <schemaspec> SchemaSpec
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,45 +9595,135 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES IN SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+pubobj_expr:
+ any_name
+ {
+ /* inheritance query, implicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = false;
+ $$ = n;
+ }
+ | any_name '*'
+ {
+ /* inheritance query, explicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY any_name
+ {
+ /* no inheritance */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $2;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY '(' any_name ')'
{
- $$ = (Node *) $3;
+ /* no inheritance, SQL99-style syntax */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $3;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
}
- | FOR ALL TABLES
+ | CURRENT_SCHEMA
{
- $$ = (Node *) makeInteger(true);
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = list_make1(makeString("CURRENT_SCHEMA"));
+ n->inh = false;
+ n->spl_rel_type_syn = false;
+ $$ = n;
}
;
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_UNKNOWN;
+ $$->location = @1;
+ }
+ ;
+
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
+ ;
+
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9641,6 +9735,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET ALL TABLES IN SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9656,7 +9755,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
| ALTER PUBLICATION name SET TABLE relation_expr_list
@@ -9664,7 +9763,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
| ALTER PUBLICATION name DROP TABLE relation_expr_list
@@ -9672,7 +9771,31 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->action = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name ADD_P ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -16621,6 +16744,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..1d80bb0fef 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..c83d6421a2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5479,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..3010485f47 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..42931d88f2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1630,9 +1630,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3968,21 +3972,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4133,6 +4141,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pspubid;
+ int i_psnspcid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pspubid, psnspcid "
+ "FROM pg_catalog.pg_publication_schema");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pspubid = PQfnumber(res, "pspubid");
+ i_psnspcid = PQfnumber(res, "psnspcid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, i, i_pspubid));
+ Oid psnspcid = atooid(PQgetvalue(res, i, i_psnspcid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(psnspcid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4220,6 +4316,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10445,6 +10579,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18693,6 +18830,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..17ca7aeb7b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -629,6 +631,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +748,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8333558bda..d1e5b79674 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,12 +6407,9 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
printTableContent cont;
@@ -6328,6 +6445,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6455,19 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6480,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7cdfc7c637..a3b7279bb9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2637,15 +2646,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..508b663639 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -106,10 +106,18 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaOid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..47420e5dbf 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -17,12 +17,42 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+/*
+ * Invalidate the relations.
+ */
+static inline void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point it's
+ * cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+
+ CacheInvalidateRelcacheByRelid(relid);
+ }
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6a4d82f0a8..af82a2fd7f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -482,7 +482,9 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7af13dee43..4654c6b7a7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,46 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* type of this schemaspec */
+ char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_SCHEMA, /* Schema type */
+ PUBLICATIONOBJ_SEQUENCE, /* Sequence type */
+ PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ List *name; /* publication object name */
+ bool inh; /* expand rel by inheritance? recursively act
+ * on children? */
+ bool spl_rel_type_syn; /* true if it is special relation type
+ * syntax */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1845,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3631,6 +3672,8 @@ typedef struct CreatePublicationStmt
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
List *tables; /* Optional list of tables to add */
+ List *schemas; /* Optional list of schemas to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3642,10 +3685,12 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
+ /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE/SCHEMA */
List *tables; /* List of tables to add/drop */
+ List *schemas; /* List of schemas to add/drop/set */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..92cb36d161 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -60,15 +60,15 @@ CREATE TABLE testpub_tbl2 (id serial primary key, data text);
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
-- fail - can't drop from all tables publication
ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..3b4f62025f 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -141,6 +141,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..e013675541 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -777,6 +777,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -833,6 +834,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2043,8 +2045,11 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2329,6 +2334,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v21-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v21-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From 0fefce02f04d10c8ac9912f9b117437465d62b0d Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Tue, 24 Aug 2021 19:32:18 +0530
Subject: [PATCH v21 2/2] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 73 ++-
doc/src/sgml/ref/alter_publication.sgml | 47 +-
doc/src/sgml/ref/create_publication.sgml | 54 ++-
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 468 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 211 ++++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++-
9 files changed, 1024 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..83d14cc3b6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6169,6 +6174,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6242,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11343,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..21788f7176 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -63,6 +77,8 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
+ The <literal>ADD SCHEMA</literal> and <literal>SET SCHEMA</literal> to a
+ publication requires the invoking user to be a superuser.
To alter the owner, you must also be a direct or indirect member of the new
owning role. The new owner must have <literal>CREATE</literal> privilege on
the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
@@ -97,6 +113,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +166,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..b868b3f437 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | [ FOR { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ], }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ] } ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -170,9 +182,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ and <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +235,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for users and departments
+ table and that publishes all changes for all the tables present in the
+ schema "production" and`:
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a4ee54d516..f19f279d55 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2301,6 +2301,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2337,6 +2346,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..bd60f72f78 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 92cb36d161..78d101c411 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,92 @@ DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables:
+ "public.testpub_tbl1"
+Schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to 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
+Tables:
+ "pub_test.testpub_nopk"
+Schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+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
+Schemas:
+ "pub_test"
+
+-- 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
+Tables:
+ "pub_test.testpub_nopk"
+Schemas:
+ "pub_test"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +180,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -246,18 +332,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +378,386 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schemas cannot be added to publications.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+ERROR: schema "pub_test2" is 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
+Schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN schema pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+Tables:
+ "pub_test2.tbl1"
+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
+Tables:
+ "pub_test2.tbl1"
+Schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: for table/for all tables in schema should be specified before the object
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..49822d7ed4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..1df32f467f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -133,9 +166,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -143,12 +178,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +204,183 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN schema pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN schema pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 0c84d87873..0a479dfe36 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = PostgresNode->new('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.30.2
On Sat, Aug 14, 2021 at 3:02 PM Peter Eisentraut <
peter.eisentraut@enterprisedb.com> wrote:
On 13.08.21 04:59, Amit Kapila wrote:
Even if we drop all tables added to the publication from it, 'pubkind'
doesn't go back to 'empty'. Is that intentional behavior? If we do
that, we can save the lookup of pg_publication_rel and
pg_publication_schema in some cases, and we can switch the publication
that was created as FOR SCHEMA to FOR TABLE and vice versa.Do we really want to allow users to change a publication that is FOR
SCHEMA to FOR TABLE? I see that we don't allow to do that FOR TABLES.
postgres=# Alter Publication pub add table tbl1;
ERROR: publication "pub" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES
publications.
I think the strict separation between publication-for-tables vs.
publication-for-schemas is a mistake. Why can't I have a publication
that publishes tables t1, t2, t3, *and* schemas s1, s2, s3. Also note
that we have a pending patch to add sequences support to logical
replication. So eventually, a publication will be able to contain a
bunch of different objects of different kinds.
Thanks for the feedback, now the same publication can handle schemas as
well as tables, and can be extended further to other objects. This is
handled in the v21 patch attached at [1]/messages/by-id/CALDaNm2iKJvSdCyh0S+wYgFjMNB4hu3kYjk=YrEkpqTJY9zW+w@mail.gmail.com. It is supported by the following
syntax:
CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA s1,s2,s3, TABLE t1,t2,t3;
[1]: /messages/by-id/CALDaNm2iKJvSdCyh0S+wYgFjMNB4hu3kYjk=YrEkpqTJY9zW+w@mail.gmail.com
/messages/by-id/CALDaNm2iKJvSdCyh0S+wYgFjMNB4hu3kYjk=YrEkpqTJY9zW+w@mail.gmail.com
Regards,
Vignesh
On Wednesday, August 25, 2021 5:37 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v21 patch has the changes based on the new syntax and fixes
few of the other review comments provided by reviewers.
Thanks for your new patch. I saw the following warning when building, please have a look.
publicationcmds.c: In function ‘ConvertPubObjSpecListToOidList’:
publicationcmds.c:212:23: warning: ‘prevobjtype’ may be used uninitialized in this function [-Wmaybe-uninitialized]
pubobj->pubobjtype = prevobjtype;
~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
Regards
Tang
On Wed, Aug 25, 2021 at 3:07 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 25, 2021 at 1:19 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Mon, Aug 23, 2021 at 11:16 PM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Aug 17, 2021 at 6:55 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Amit Kapila <amit.kapila16@gmail.com> writes:
On Tue, Aug 17, 2021 at 6:40 AM Peter Smith <smithpb2250@gmail.com> wrote:
On Mon, Aug 16, 2021 at 11:31 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Abstractly it'd be
createpub := CREATE PUBLICATION pubname FOR cpitem [, ... ] [ WITH ... ]
cpitem := ALL TABLES |
TABLE name |
ALL TABLES IN SCHEMA name |
ALL SEQUENCES |
SEQUENCE name |
ALL SEQUENCES IN SCHEMA name |
nameThe grammar output would need some post-analysis to attribute the
right type to bare "name" items, but that doesn't seem difficult.That last bare "name" cpitem. looks like it would permit the following syntax:
CREATE PUBLICATION pub FOR a,b,c;
Was that intentional?I think so.
I had supposed that we could throw an error at the post-processing stage,
or alternatively default to assuming that such names are tables.Now you could instead make the grammar work like
cpitem := ALL TABLES |
TABLE name [, ...] |
ALL TABLES IN SCHEMA name [, ...] |
ALL SEQUENCES |
SEQUENCE name [, ...] |
ALL SEQUENCES IN SCHEMA name [, ...]which would result in a two-level-list data structure. I'm not sure
that this is better, as any sort of mistake would result in a very
uninformative generic "syntax error" from Bison. Errors out of a
post-processing stage could be more specific than that.I preferred the implementation in the lines Tom Lane had proposed at [1]. Is it ok if the implementation is something like below:
CreatePublicationStmt:
CREATE PUBLICATION name FOR pub_obj_list opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
n->options = $6;
n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
| pub_obj_list ',' PublicationObjSpec
{ $$ = lappend($1, $3); }
;
/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
PublicationObjSpec: TABLE pubobj_expr
{ ....}
| ALL TABLES IN_P SCHEMA pubobj_expr
{ ....}
| pubobj_expr
{ ....}
;
pubobj_expr:
any_name
{ ....}
| any_name '*'
{ ....}
| ONLY any_name
{ ....}
| ONLY '(' any_name ')'
{ ....}
| CURRENT_SCHEMA
{ ....}
;"FOR ALL TABLES” (that includes all tables in the database) is missing
in this syntax?"FOR ALL TABLES" is present in CreatePublicationStmt rule, sorry for
not including all of CreatePublicationStmt rule in the previous mail,
I thought of keeping the contents shorter:CreatePublicationStmt:
CREATE PUBLICATION name opt_definition
{....}
| CREATE PUBLICATION name FOR ALL TABLES opt_definition
{....}
| CREATE PUBLICATION name FOR pub_obj_list opt_definition
{....}
;It is not in pub_obj_list as the user will be able to specify either
of "FOR ALL TABLES" or "FOR TABLE/ FOR ALL TABLES IN SCHEMA" along
with create publication.I needed pubobj_expr to support the existing syntaxes supported by relation_expr and also to handle CURRENT_SCHEMA support in case of the "FOR ALL TABLES IN SCHEMA" feature. I changed the name to any_name to support objects like "sch1.t1".
I think that relation_expr also accepts objects like "sch1.t1", no?
Earlier syntax only supported relations, the relations were parsed
into RangeVar datatype. The new feature supports schema for which only
the schema name is required. To keep the parsing rule common, I used
any_name which will store the dotted name into a list for both
relation and schema. I will later convert it into rangevar for
relation and schema oid for schema names during the processing and
create the publication. I felt relation_expr was able to handle dotted
names because of qualified_name having "ColId indirection", here
indirection rule takes care of handling the dotted names.I felt if a user specified "FOR ALL TABLES", the user should not be allowed to combine it with "FOR TABLE" and "FOR ALL TABLES IN SCHEMA" as "FOR ALL TABLES" anyway will include all the tables.
I think so too.
Should we support the similar syntax in case of alter publication, like "ALTER PUBLICATION pub1 ADD TABLE t1,t2, ALL TABLES IN SCHEMA sch1, sch2" or shall we keep these separate like "ALTER PUBLICATION pub1 ADD TABLE t1, t2" and "ALTER PUBLICATION pub1 ADD ALL TABLES IN SCHEMA sch1, sch2". I preferred to keep it separate as we have kept ADD/DROP separately which cannot be combined currently.
If we support the former syntax, the latter two syntaxes are also
supported. Why do we want to support only the latter separate two
syntaxes?We can support either syntax, I was not sure which one is better. If
alter also should support similar syntax I can do it as a separate
patch so as to not increase the main patch size. Thoughts?
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.
Thoughts?
Regards,
Vignesh
Attachments:
v22-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v22-0001-Added-schema-level-support-for-publication.patchDownload
From 1465c58c0705d445a6e5cef80089c9299e5568cf Mon Sep 17 00:00:00 2001
From: Vigneshwaran c <vignesh21@gmail.com>
Date: Mon, 26 Jul 2021 09:25:22 +0530
Subject: [PATCH v22 1/3] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 141 +++++++
src/backend/catalog/pg_publication.c | 331 ++++++++++++++--
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 398 ++++++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 5 +-
src/backend/nodes/equalfuncs.c | 5 +-
src/backend/parser/gram.y | 193 ++++++++--
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 4 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++-
src/bin/pg_dump/pg_dump.h | 15 +
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 189 ++++++++--
src/bin/psql/tab-complete.c | 22 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 8 +
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 26 ++
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 49 ++-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 6 +-
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 7 +
34 files changed, 1529 insertions(+), 147 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..07abcbae92 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -829,6 +830,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1125,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1946,47 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2259,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2352,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2847,6 +2901,49 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ *pubname = get_publication_name(psform->pspubid, false);
+ *nspname = get_namespace_name(psform->psnspcid);
+ if (!(*nspname))
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3902,6 +3999,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4589,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5828,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..3bd97c3207 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -214,6 +216,96 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schemas cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schemas cannot be added to publications.")));
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ schemaRels = GetSchemaPublicationRelations(schemaoid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -238,10 +330,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,36 +399,79 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
+ systable_endscan(scan);
+ table_close(pubrelsrel, AccessShareLock);
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
+ return result;
+}
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
}
systable_endscan(scan);
- table_close(pubrelsrel, AccessShareLock);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of FOR SCHEMA publication oids associated with a specified
+ * schema oid
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONSCHEMAMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_schema) GETSTRUCT(tup))->pspubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
return result;
}
@@ -342,7 +514,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -404,6 +576,96 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets list of relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaOid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(schemaOid != InvalidOid);
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ /* Skip the relations which are not publishable */
+ if (!is_publishable_class(relForm->oid, relForm))
+ continue;
+
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR SCHEMA publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaOid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -533,10 +795,25 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ if (!relids || !schemarelids)
+ tables = list_concat(relids, schemarelids);
+ else
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..35f47d3253 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..e694c67369 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -34,25 +36,26 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -138,6 +141,142 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ConvertPubObjSpecListToOidList(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_UNKNOWN;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("for table/for all tables in schema should be specified before the object"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ RangeVar *rel;
+ char *relname = strVal(linitial(pubobj->name));
+
+ if (list_length(pubobj->name) == 1 &&
+ (strcmp(relname, "CURRENT_SCHEMA") == 0))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid relation name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ rel = makeRangeVarFromNameList(pubobj->name);
+ rel->inh = pubobj->inh;
+ rel->location = pubobj->location;
+ rel->relpersistence = RELPERSISTENCE_PERMANENT;
+ *rels = lappend(*rels, rel);
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_SCHEMA)
+ {
+ Oid schemaoid;
+ char *schemaname;
+
+ if (list_length(pubobj->name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(pubobj->name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ if (pubobj->spl_rel_type_syn)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(linitial(pubobj->name));
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+ char *nspname;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+ }
+ else
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaoid);
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -155,6 +294,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaoidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -224,19 +365,40 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ConvertPubObjSpecListToOidList(stmt->pubobjects, pstate, &relations,
+ &schemaoidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- table_close(rel, RowExclusiveLock);
+ if (schemaoidlist != NIL)
+ {
+ Assert(list_length(schemaoidlist) > 0);
+
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of create
+ * publication command.
+ */
+ LockSchemaList(schemaoidlist);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ }
+ table_close(rel, RowExclusiveLock);
InvokeObjectPostCreateHook(PublicationRelationId, puboid, 0);
if (wal_level != WAL_LEVEL_LOGICAL)
@@ -316,31 +478,21 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
-
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat(relids, schemarelids);
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
@@ -367,15 +519,17 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ errdetail("Tables cannot be added, dropped or set on FOR ALL TABLES publications.")));
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
- if (stmt->tableAction == DEFELEM_ADD)
+ if (stmt->action == DEFELEM_ADD)
+ {
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -426,11 +580,70 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
+{
+ List *schemaoidlist = NIL;
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added, dropped or set on FOR ALL TABLES publications.")));
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /* Convert the text list into oid list */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaoidlist);
+ if (stmt->action == DEFELEM_ADD)
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaoidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -459,6 +672,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup);
else
AlterPublicationTables(stmt, rel, tup);
@@ -497,6 +712,58 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_schema pubsch;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ schemaRels = GetSchemaPublicationRelations(pubsch->psnspcid,
+ PUBLICATION_PART_ALL);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaoid, 0, AccessShareLock);
+ }
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -631,6 +898,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -665,6 +965,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..3e57a152f4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12253,6 +12253,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..bcb937c7be 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4808,7 +4808,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4822,8 +4822,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(schemas);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..05ca195af8 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2295,7 +2295,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2308,8 +2308,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(schemas);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 39a2849eba..e408acfce3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +429,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,7 +556,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
-
+%type <schemaspec> SchemaSpec
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,45 +9595,135 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES IN SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+pubobj_expr:
+ any_name
+ {
+ /* inheritance query, implicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = false;
+ $$ = n;
+ }
+ | any_name '*'
+ {
+ /* inheritance query, explicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY any_name
+ {
+ /* no inheritance */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $2;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY '(' any_name ')'
{
- $$ = (Node *) $3;
+ /* no inheritance, SQL99-style syntax */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $3;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
}
- | FOR ALL TABLES
+ | CURRENT_SCHEMA
{
- $$ = (Node *) makeInteger(true);
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = list_make1(makeString("CURRENT_SCHEMA"));
+ n->inh = false;
+ n->spl_rel_type_syn = false;
+ $$ = n;
}
;
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_UNKNOWN;
+ $$->location = @1;
+ }
+ ;
+
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
+ ;
+
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9641,6 +9735,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET ALL TABLES IN SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9656,7 +9755,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
| ALTER PUBLICATION name SET TABLE relation_expr_list
@@ -9664,7 +9763,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
| ALTER PUBLICATION name DROP TABLE relation_expr_list
@@ -9672,7 +9771,31 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->action = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name ADD_P ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -16621,6 +16744,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..1d80bb0fef 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..c83d6421a2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5479,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..3010485f47 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..42931d88f2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1630,9 +1630,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3968,21 +3972,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4133,6 +4141,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pspubid;
+ int i_psnspcid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pspubid, psnspcid "
+ "FROM pg_catalog.pg_publication_schema");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pspubid = PQfnumber(res, "pspubid");
+ i_psnspcid = PQfnumber(res, "psnspcid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, i, i_pspubid));
+ Oid psnspcid = atooid(PQgetvalue(res, i, i_psnspcid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(psnspcid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4220,6 +4316,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10445,6 +10579,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18693,6 +18830,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..17ca7aeb7b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -629,6 +631,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +748,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8333558bda..d1e5b79674 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,12 +6407,9 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
printTableContent cont;
@@ -6328,6 +6445,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6455,19 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6480,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7cdfc7c637..a3b7279bb9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1640,10 +1640,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2637,15 +2646,20 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..508b663639 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -106,10 +106,18 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaOid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..9cc74a0d19 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -17,12 +17,38 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+/*
+ * Invalidate the relations.
+ */
+static inline void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point it's
+ * cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6a4d82f0a8..af82a2fd7f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -482,7 +482,9 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7af13dee43..7175ae1fe5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,46 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* type of this schemaspec */
+ char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_SCHEMA, /* Schema type */
+ PUBLICATIONOBJ_SEQUENCE, /* Sequence type */
+ PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ List *name; /* publication object name */
+ bool inh; /* expand rel by inheritance? recursively act
+ * on children? */
+ bool spl_rel_type_syn; /* true if it is special relation type
+ * syntax */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1845,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3630,7 +3671,7 @@ typedef struct CreatePublicationStmt
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3642,10 +3683,12 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
+ /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE/SCHEMA */
List *tables; /* List of tables to add/drop */
+ List *schemas; /* List of schemas to add/drop/set */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..92cb36d161 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -60,15 +60,15 @@ CREATE TABLE testpub_tbl2 (id serial primary key, data text);
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
-- fail - can't drop from all tables publication
ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..3b4f62025f 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -141,6 +141,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 621d0cb4da..7f5d0e16df 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -834,6 +835,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2044,8 +2046,11 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2330,6 +2335,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v22-0002-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v22-0002-Tests-and-documentation-for-schema-level-support.patchDownload
From af566a22238018cd363cb42923cbf4d92a557993 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Tue, 24 Aug 2021 19:32:18 +0530
Subject: [PATCH v22 2/3] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 73 ++-
doc/src/sgml/ref/alter_publication.sgml | 47 +-
doc/src/sgml/ref/create_publication.sgml | 54 ++-
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 468 ++++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 211 ++++++++-
src/test/subscription/t/001_rep_changes.pl | 150 +++++-
9 files changed, 1024 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..83d14cc3b6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6169,6 +6174,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -6236,6 +6242,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11343,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..21788f7176 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -63,6 +77,8 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
+ The <literal>ADD SCHEMA</literal> and <literal>SET SCHEMA</literal> to a
+ publication requires the invoking user to be a superuser.
To alter the owner, you must also be a direct or indirect member of the new
owning role. The new owner must have <literal>CREATE</literal> privilege on
the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
@@ -97,6 +113,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +166,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..b868b3f437 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | [ FOR { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ], }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ] } ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -170,9 +182,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ and <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +235,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for users and departments
+ table and that publishes all changes for all the tables present in the
+ schema "production" and`:
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a4ee54d516..f19f279d55 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2301,6 +2301,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2337,6 +2346,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..bd60f72f78 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 92cb36d161..4bc2edf796 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,92 @@ DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added, dropped or set on FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables:
+ "public.testpub_tbl1"
+Schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to 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
+Tables:
+ "pub_test.testpub_nopk"
+Schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+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
+Schemas:
+ "pub_test"
+
+-- 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
+Tables:
+ "pub_test.testpub_nopk"
+Schemas:
+ "pub_test"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +180,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -246,18 +332,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +378,386 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: System schemas cannot be added to publications.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+ERROR: schema "pub_test2" is 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
+Schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+Tables:
+ "pub_test2.tbl1"
+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
+Tables:
+ "pub_test2.tbl1"
+Schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: for table/for all tables in schema should be specified before the object
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..49822d7ed4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..cae96e7662 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -133,9 +166,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -143,12 +178,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +204,183 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 0c84d87873..0a479dfe36 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
# Initialize publisher node
my $node_publisher = PostgresNode->new('publisher');
@@ -275,6 +275,154 @@ $node_publisher->safe_psql('postgres', "DROP TABLE temp2");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp1");
$node_subscriber->safe_psql('postgres', "DROP TABLE temp2");
+# Test replication with publications created using FOR SCHEMA option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication for schema sch1 and sch2 that will only be used for
+# this test
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+
# add REPLICA IDENTITY FULL so we can update
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
--
2.30.2
v22-0003-Alter-publication-syntax-enhancement-to-keep-it-.patchtext/x-patch; charset=US-ASCII; name=v22-0003-Alter-publication-syntax-enhancement-to-keep-it-.patchDownload
From bcee1a20adbd31b1af78bdd2c5b9671e948421e2 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Fri, 27 Aug 2021 09:23:47 +0530
Subject: [PATCH v22 3/3] Alter publication syntax enhancement to keep it
similar to create publication.
Alter publication syntax enhancement to keep it similar to create
publication to support FOR TABLE, FOR ALL TABLES IN SCHEMA syntax.
---
src/backend/commands/publicationcmds.c | 68 ++++++-----------------
src/backend/nodes/copyfuncs.c | 3 +-
src/backend/nodes/equalfuncs.c | 3 +-
src/backend/parser/gram.y | 75 +++-----------------------
src/include/nodes/parsenodes.h | 20 +------
src/tools/pgindent/typedefs.list | 2 -
6 files changed, 26 insertions(+), 145 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index e694c67369..0f2ee6173e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -141,47 +141,6 @@ parse_publication_options(ParseState *pstate,
}
}
-/*
- * Convert the SchemaSpec list into an Oid list.
- */
-static List *
-ConvertSchemaSpecListToOidList(List *schemas)
-{
- List *schemaoidlist = NIL;
- ListCell *cell;
-
- foreach(cell, schemas)
- {
- SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
- Oid schemaoid;
- List *search_path;
- char *nspname;
-
- if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
- {
- search_path = fetch_search_path(false);
- if (search_path == NIL) /* nothing valid in search_path? */
- ereport(ERROR,
- errcode(ERRCODE_UNDEFINED_SCHEMA),
- errmsg("no schema has been selected"));
-
- schemaoid = linitial_oid(search_path);
- nspname = get_namespace_name(schemaoid);
- if (nspname == NULL) /* recently-deleted namespace? */
- ereport(ERROR,
- errcode(ERRCODE_UNDEFINED_SCHEMA),
- errmsg("no schema has been selected"));
- }
- else
- schemaoid = get_namespace_oid(schema->schemaname, false);
-
- /* Filter out duplicates if user specifies "sch1, sch1" */
- schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
- }
-
- return schemaoidlist;
-}
-
/*
* Convert the PublicationObjSpecType list into schema oid list and rangevar
* list.
@@ -507,7 +466,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
*/
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+ HeapTuple tup, List *tables)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
@@ -521,9 +480,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
NameStr(pubform->pubname)),
errdetail("Tables cannot be added, dropped or set on FOR ALL TABLES publications.")));
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(tables) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(tables);
if (stmt->action == DEFELEM_ADD)
{
@@ -586,9 +545,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
* Add/Remove/Set the schemas to/from publication.
*/
static void
-AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, List *schemaoidlist)
{
- List *schemaoidlist = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Check that user is allowed to manipulate the publication tables */
@@ -605,9 +564,6 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to add or set schemas")));
- /* Convert the text list into oid list */
- schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
-
/*
* Schema lock is held until the publication is altered to prevent
* concurrent schema deletion. No need to unlock the schemas, the locks
@@ -651,6 +607,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
Relation rel;
HeapTuple tup;
Form_pg_publication pubform;
+ List *relations = NIL;
+ List *schemaoidlist = NIL;
rel = table_open(PublicationRelationId, RowExclusiveLock);
@@ -670,12 +628,18 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
stmt->pubname);
+ ConvertPubObjSpecListToOidList(stmt->pubobjects, pstate, &relations,
+ &schemaoidlist);
+
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
- else if (stmt->schemas)
- AlterPublicationSchemas(stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ if (schemaoidlist)
+ AlterPublicationSchemas(stmt, rel, tup, schemaoidlist);
+ if (relations)
+ AlterPublicationTables(stmt, rel, tup, relations);
+ }
/* Cleanup. */
heap_freetuple(tup);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bcb937c7be..4f15214d8c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4821,8 +4821,7 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
- COPY_NODE_FIELD(schemas);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
COPY_SCALAR_FIELD(action);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 05ca195af8..14af6b9937 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2307,8 +2307,7 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
- COMPARE_NODE_FIELD(schemas);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
COMPARE_SCALAR_FIELD(action);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e408acfce3..53b5d012d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,7 +169,6 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
-static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -258,7 +257,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
- SchemaSpec *schemaspec;
PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
@@ -429,7 +427,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list schema_list pub_obj_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -556,7 +554,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
-%type <schemaspec> SchemaSpec
%type <publicationobjectspec> PublicationObjSpec
%type <publicationobjectspec> pubobj_expr
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -9705,26 +9702,6 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-/* Schema specifications */
-SchemaSpec: ColId
- {
- SchemaSpec *n;
- n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
- n->schemaname = pstrdup($1);
- $$ = n;
- }
- | CURRENT_SCHEMA
- {
- $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
- }
- ;
-
-schema_list: SchemaSpec
- { $$ = list_make1($1); }
- | schema_list ',' SchemaSpec
- { $$ = lappend($1, $3); }
- ;
-
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
@@ -9750,51 +9727,27 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE relation_expr_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
+ n->pubobjects = $5;
n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE relation_expr_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
+ n->pubobjects = $5;
n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE relation_expr_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->action = DEFELEM_DROP;
- $$ = (Node *)n;
- }
- | ALTER PUBLICATION name ADD_P ALL TABLES IN_P SCHEMA schema_list
- {
- AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
- n->pubname = $3;
- n->schemas = $9;
- n->action = DEFELEM_ADD;
- $$ = (Node *)n;
- }
- | ALTER PUBLICATION name SET ALL TABLES IN_P SCHEMA schema_list
- {
- AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
- n->pubname = $3;
- n->schemas = $9;
- n->action = DEFELEM_SET;
- $$ = (Node *)n;
- }
- | ALTER PUBLICATION name DROP ALL TABLES IN_P SCHEMA schema_list
- {
- AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
- n->pubname = $3;
- n->schemas = $9;
+ n->pubobjects = $5;
n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
@@ -16744,20 +16697,6 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
-/*
- * makeSchemaSpec - Create a SchemaSpec with the given type and location
- */
-static SchemaSpec *
-makeSchemaSpec(SchemaSpecType type, int location)
-{
- SchemaSpec *spec = makeNode(SchemaSpec);
-
- spec->schematype = type;
- spec->location = location;
-
- return spec;
-}
-
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7175ae1fe5..9153466f2c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,23 +341,6 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
-/*
- * SchemaSpec - a schema name or CURRENT_SCHEMA
- */
-typedef enum SchemaSpecType
-{
- SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
- SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
-} SchemaSpecType;
-
-typedef struct SchemaSpec
-{
- NodeTag type;
- SchemaSpecType schematype; /* type of this schemaspec */
- char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
- int location; /* token location, or -1 if unknown */
-} SchemaSpec;
-
/*
* Publication object type
*/
@@ -3684,8 +3667,7 @@ typedef struct AlterPublicationStmt
List *options; /* List of DefElem nodes */
/* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE/SCHEMA */
- List *tables; /* List of tables to add/drop */
- List *schemas; /* List of schemas to add/drop/set */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction action; /* What action to perform with the
* tables/schemas */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 7f5d0e16df..d0e3179519 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2335,8 +2335,6 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
-SchemaSpec
-SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
On Thu, Aug 26, 2021 at 7:52 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Wednesday, August 25, 2021 5:37 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v21 patch has the changes based on the new syntax and fixes
few of the other review comments provided by reviewers.Thanks for your new patch. I saw the following warning when building, please have a look.
publicationcmds.c: In function ‘ConvertPubObjSpecListToOidList’:
publicationcmds.c:212:23: warning: ‘prevobjtype’ may be used uninitialized in this function [-Wmaybe-uninitialized]
pubobj->pubobjtype = prevobjtype;
~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
Thanks for reporting this, I have fixed this in the v22 patch attached at [1]/messages/by-id/CALDaNm1rNDbGwoo0FC9vF1BmueUy__u1ZM5yYOjEQW1Of6zdWQ@mail.gmail.com.
[1]: /messages/by-id/CALDaNm1rNDbGwoo0FC9vF1BmueUy__u1ZM5yYOjEQW1Of6zdWQ@mail.gmail.com
Regards,
VIgnesh
On Fri, Aug 27, 2021 at 11:43 AM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 25, 2021 at 3:07 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.
Few comments on v22-0001-Added-schema-level-support-for-publication:
========================================================
1. Why in publication_add_schema(), you are registering invalidation
for all schema relations? It seems this is to allow rebuilding the
publication info for decoding sessions. But that is not required as
you are registering rel_sync_cache_publication_cb for
publication_schema relation. In rel_sync_cache_publication_cb, we are
marking replicate_valid as false for each entry which will allow
publication info to be rebuilt in get_rel_sync_entry.
I see that it is done for a single relation in the current code in
function publication_add_relation but I guess that is also not
required. You can once test this. If you still think it is required,
can you please explain me why and then we can accordingly add some
comments in the patch.
Peter E., Sawada-San, can you please let me know if I am missing
something in this regard? In the code, I see a comment "/* Invalidate
relcache so that publication info is rebuilt. */" in function
publication_add_relation() but I don't understand why it is required
as per my explanation above?
2.
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_SCHEMA, /* Schema type */
+ PUBLICATIONOBJ_SEQUENCE, /* Sequence type */
Why add anything related to the sequence in this patch?
3.
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
Why the schema name handling is different from publication name? Why
can't we pass missing_ok for schema api and handle it similar
publication api?
4. In getPublicationSchemaInfo(), why the missing_ok flag is not used
in get_publication_name() whereas it is used for all other syscache
searches in that function?
5. Don't we need to expose a view for publication schemas similar to
pg_publication_tables?
6.
publication_add_schema()
{
..
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("System schemas cannot be added to publications.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schemas cannot be added to publications.")));
..
}
Can we change the first detail message as: "This operation is not
supported for system schemas." and the second detail message as:
"Temporary schemas cannot be replicated."? This is to make these
messages similar to corresponding messages for relations in function
check_publication_add_relation(). Can we move these checks to a
separate function?
--
With Regards,
Amit Kapila.
On Fri, Aug 27, 2021 at 4:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 27, 2021 at 11:43 AM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 25, 2021 at 3:07 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.Few comments on v22-0001-Added-schema-level-support-for-publication:
========================================================
1. Why in publication_add_schema(), you are registering invalidation
for all schema relations? It seems this is to allow rebuilding the
publication info for decoding sessions. But that is not required as
you are registering rel_sync_cache_publication_cb for
publication_schema relation. In rel_sync_cache_publication_cb, we are
marking replicate_valid as false for each entry which will allow
publication info to be rebuilt in get_rel_sync_entry.I see that it is done for a single relation in the current code in
function publication_add_relation but I guess that is also not
required. You can once test this. If you still think it is required,
can you please explain me why and then we can accordingly add some
comments in the patch.
I felt this is required for handling the following concurrency scenario:
create schema sch1;
create table sch1.t1(c1 int);
insert into sch1.t1 values(10);
update sch1.t1 set c1 = 11;
# update will be successful and relation cache will update publication
actions based on the current state i.e no publication
create publication pub1 for all tables in schema sch1;
# After the above publication is created the relations present in this
schema should be invalidated so that the next update should fail. If
the relations are not invalidated the updates will be successful based
on previous publication actions.
update sch1.t1 set c1 = 11;
I will add comments to mention the above details. Thoughts?
Peter E., Sawada-San, can you please let me know if I am missing
something in this regard? In the code, I see a comment "/* Invalidate
relcache so that publication info is rebuilt. */" in function
publication_add_relation() but I don't understand why it is required
as per my explanation above?2. + * Publication object type + */ +typedef enum PublicationObjSpecType +{ + PUBLICATIONOBJ_TABLE, /* Table type */ + PUBLICATIONOBJ_SCHEMA, /* Schema type */ + PUBLICATIONOBJ_SEQUENCE, /* Sequence type */Why add anything related to the sequence in this patch?
I will handle this in my next version.
3. +get_object_address_publication_schema(List *object, bool missing_ok) +{ + ObjectAddress address; + char *pubname; + Publication *pub; + char *schemaname; + Oid schemaoid; + + ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid); + + /* Fetch schema name and publication name from input list */ + schemaname = strVal(linitial(object)); + pubname = strVal(lsecond(object)); + + schemaoid = get_namespace_oid(schemaname, false); + + /* Now look up the pg_publication tuple */ + pub = GetPublicationByName(pubname, missing_ok); + if (!pub) + return address;Why the schema name handling is different from publication name? Why
can't we pass missing_ok for schema api and handle it similar
publication api?
I will handle this in my next version.
4. In getPublicationSchemaInfo(), why the missing_ok flag is not used
in get_publication_name() whereas it is used for all other syscache
searches in that function?
I will handle this in my next version.
5. Don't we need to expose a view for publication schemas similar to
pg_publication_tables?
pg_publication_tables is a common view for both "FOR TABLE", "FOR ALL
TABLES" and "FOR ALL TABLES IN SCHEMA", this view will internally
access pg_publication_rel and pg_publication_schema to get the
corresponding tables. I felt we don't need a separate view for
publication schemas. Thoughts?
6. publication_add_schema() { .. + /* Can't be system namespace */ + if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" is a system schema", + get_namespace_name(schemaoid)), + errdetail("System schemas cannot be added to publications."))); + + /* Can't be temporary namespace */ + if (isAnyTempNamespace(schemaoid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" is a temporary schema", + get_namespace_name(schemaoid)), + errdetail("Temporary schemas cannot be added to publications."))); .. }Can we change the first detail message as: "This operation is not
supported for system schemas." and the second detail message as:
"Temporary schemas cannot be replicated."? This is to make these
messages similar to corresponding messages for relations in function
check_publication_add_relation(). Can we move these checks to a
separate function?
I will handle this in my next version.
Regards,
Vignesh
On Fri, Aug 27, 2021 at 6:09 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Aug 27, 2021 at 4:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 27, 2021 at 11:43 AM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 25, 2021 at 3:07 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.Few comments on v22-0001-Added-schema-level-support-for-publication:
========================================================
1. Why in publication_add_schema(), you are registering invalidation
for all schema relations? It seems this is to allow rebuilding the
publication info for decoding sessions. But that is not required as
you are registering rel_sync_cache_publication_cb for
publication_schema relation. In rel_sync_cache_publication_cb, we are
marking replicate_valid as false for each entry which will allow
publication info to be rebuilt in get_rel_sync_entry.I see that it is done for a single relation in the current code in
function publication_add_relation but I guess that is also not
required. You can once test this. If you still think it is required,
can you please explain me why and then we can accordingly add some
comments in the patch.I felt this is required for handling the following concurrency scenario:
create schema sch1;
create table sch1.t1(c1 int);
insert into sch1.t1 values(10);
update sch1.t1 set c1 = 11;
# update will be successful and relation cache will update publication
actions based on the current state i.e no publication
create publication pub1 for all tables in schema sch1;
# After the above publication is created the relations present in this
schema should be invalidated so that the next update should fail.
What do you mean by update should fail? I think all the relations in
RelationSyncCache via rel_sync_cache_publication_cb because you have
updated pg_publication_schema and that should register syscache
invalidation.
If
the relations are not invalidated the updates will be successful based
on previous publication actions.
update sch1.t1 set c1 = 11;
I think even without special relcache invalidations the relations
should be invalidated because of syscache invalidation as mentioned in
the previous point. Am I missing something here?
5. Don't we need to expose a view for publication schemas similar to
pg_publication_tables?pg_publication_tables is a common view for both "FOR TABLE", "FOR ALL
TABLES" and "FOR ALL TABLES IN SCHEMA", this view will internally
access pg_publication_rel and pg_publication_schema to get the
corresponding tables. I felt we don't need a separate view for
publication schemas. Thoughts?
Don't you think some users might want to know all the schema names for
a publication? I am not completely sure on this point but I think it
is good to have information for users. It might be also useful to have
pg_publication_objects where we can display object types (like table,
schema, sequence, etc) and then object names. If you are not convinced
then we can wait and see what others think about this.
--
With Regards,
Amit Kapila.
On Sat, Aug 28, 2021 at 3:19 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 27, 2021 at 6:09 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Aug 27, 2021 at 4:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 27, 2021 at 11:43 AM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 25, 2021 at 3:07 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.Few comments on v22-0001-Added-schema-level-support-for-publication:
========================================================
1. Why in publication_add_schema(), you are registering invalidation
for all schema relations? It seems this is to allow rebuilding the
publication info for decoding sessions. But that is not required as
you are registering rel_sync_cache_publication_cb for
publication_schema relation. In rel_sync_cache_publication_cb, we are
marking replicate_valid as false for each entry which will allow
publication info to be rebuilt in get_rel_sync_entry.I see that it is done for a single relation in the current code in
function publication_add_relation but I guess that is also not
required. You can once test this. If you still think it is required,
can you please explain me why and then we can accordingly add some
comments in the patch.I felt this is required for handling the following concurrency scenario:
create schema sch1;
create table sch1.t1(c1 int);
insert into sch1.t1 values(10);
update sch1.t1 set c1 = 11;
# update will be successful and relation cache will update publication
actions based on the current state i.e no publication
create publication pub1 for all tables in schema sch1;
# After the above publication is created the relations present in this
schema should be invalidated so that the next update should fail.What do you mean by update should fail? I think all the relations in
RelationSyncCache via rel_sync_cache_publication_cb because you have
updated pg_publication_schema and that should register syscache
invalidation.
By update should fail, I meant the updates without setting replica
identity before creating the decoding context. The scenario is like
below (all in the same session, the subscription is not created):
create schema sch1;
create table sch1.t1(c1 int);
insert into sch1.t1 values(10);
# Before updating we will check CheckCmdReplicaIdentity, as there are
no publications on this table rd_pubactions will be set accordingly in
relcache entry.
update sch1.t1 set c1 = 11;
# Now we will create the publication after rd_pubactions has been set
in the cache. Now when we create this publication we should invalidate
the relations present in the schema, this is required so that when the
next update happens, we should check the publication actions again in
CheckCmdReplicaIdentity and fail the update which does not set
replica identity after the publication is created.
create publication pub1 for all tables in schema sch1;
# After the above publication is created the relations present in this
schema will be invalidated. Now we will check the publication actions
again in CheckCmdReplicaIdentity and the update will fail.
update sch1.t1 set c1 = 11;
ERROR: cannot update table "t1" because it does not have a replica
identity and publishes updates
HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
The rel_sync_cache_publication_cb invalidations are registered once
the decoding context is created. Since the decoding context is not
created at this point of time we should explicitly invalidate all the
relations present in this schema by calling InvalidatePublicationRels.
If
the relations are not invalidated the updates will be successful based
on previous publication actions.
update sch1.t1 set c1 = 11;I think even without special relcache invalidations the relations
should be invalidated because of syscache invalidation as mentioned in
the previous point. Am I missing something here?
The rel_sync_cache_publication_cb invalidations are registered once
the decoding context is created. Since the decoding context is not
created at this point of time we should explicitly invalidate all the
relations present in this schema by calling InvalidatePublicationRels.
This is to prevent updates for which the replica identity is not set,
I have mentioned more details above.
5. Don't we need to expose a view for publication schemas similar to
pg_publication_tables?pg_publication_tables is a common view for both "FOR TABLE", "FOR ALL
TABLES" and "FOR ALL TABLES IN SCHEMA", this view will internally
access pg_publication_rel and pg_publication_schema to get the
corresponding tables. I felt we don't need a separate view for
publication schemas. Thoughts?Don't you think some users might want to know all the schema names for
a publication? I am not completely sure on this point but I think it
is good to have information for users. It might be also useful to have
pg_publication_objects where we can display object types (like table,
schema, sequence, etc) and then object names. If you are not convinced
then we can wait and see what others think about this.
Ok, I will add this view in the next version.
Regards,
Vignesh
On Fri, Aug 27, 2021 at 4:13 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.
Just experimenting with the new syntax so far, and seeing some new
messages and docs, I have the following suggestions for improvements:
src/backend/commands/publicationcmds.c
(1)
BEFORE:
for table/for all tables in schema should be specified before the object
AFTER:
FOR TABLE / FOR ALL TABLES IN SCHEMA should be specified before the
table/schema name(s)
(2)
BEFORE:
Tables cannot be added, dropped or set on FOR ALL TABLES publications.
AFTER:
Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
(3)
BEFORE:
Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
AFTER:
Schemas cannot be added to, dropped from, or set on FOR ALL TABLES publications.
v22-0002
doc/src/sgml/ref/create_publication.sgml
(1)
BEFORE:
+ Create a publication that publishes all changes for users and departments
+ table and that publishes all changes for all the tables present in the
AFTER:
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables
present in the
Regards,
Greg Nancarrow
Fujitsu Australia
On Friday, August 27, 2021 2:13 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to reduce the
testing effort and also it will be easier if someone disagrees with the syntax. I
will merge it to the main patch later based on the feedback. Attached v22 patch
has the changes for the same.
Thoughts?
Hi,
Here are some comments for the new version patches.
About 0001
1)
+ rel->relpersistence = RELPERSISTENCE_PERMANENT;
It seems we don't need to set this since makeRangeVarFromNameList()
already set it.
2)
+ if (!relids || !schemarelids)
+ tables = list_concat(relids, schemarelids);
+ else
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
It seems we can simplify the above code like the following:
tables = list_concat_unique_oid(relids, schemarelids);
3)
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat(relids, schemarelids);
should we invoke list_concat_unique_oid here ?
4)
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
It might be better to list_free(search_path) when not used.
5)
+ if (list_length(pubobj->name) == 1 &&
+ (strcmp(relname, "CURRENT_SCHEMA") == 0))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid relation name at or near"),
+ parser_errposition(pstate, pubobj->location));
Maybe we don't need this check, because it will report an error in
OpenTableList() anyway, "relation "CURRENT_SCHEMA" does not exist" , and that
message seems readable to me.
About 0002
6)
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index 0c84d87873..0a479dfe36 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -6,7 +6,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 32;
+use Test::More tests => 46;
I think it might be better to move these testcases create a separate perl file.
About 0003
7)
The v22-0003 seems simple and can remove lots of code in patch v22-0001, so
maybe we can merge 0001 and 0003 into one patch ?
Best regards,
Hou zj
On Sun, Aug 29, 2021 at 7:22 AM vignesh C <vignesh21@gmail.com> wrote:
On Sat, Aug 28, 2021 at 3:19 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 27, 2021 at 6:09 PM vignesh C <vignesh21@gmail.com> wrote:
What do you mean by update should fail? I think all the relations in
RelationSyncCache via rel_sync_cache_publication_cb because you have
updated pg_publication_schema and that should register syscache
invalidation.By update should fail, I meant the updates without setting replica
identity before creating the decoding context. The scenario is like
below (all in the same session, the subscription is not created):
create schema sch1;
create table sch1.t1(c1 int);
insert into sch1.t1 values(10);
# Before updating we will check CheckCmdReplicaIdentity, as there are
no publications on this table rd_pubactions will be set accordingly in
relcache entry.
update sch1.t1 set c1 = 11;
# Now we will create the publication after rd_pubactions has been set
in the cache. Now when we create this publication we should invalidate
the relations present in the schema, this is required so that when the
next update happens, we should check the publication actions again in
CheckCmdReplicaIdentity and fail the update which does not set
replica identity after the publication is created.
create publication pub1 for all tables in schema sch1;
# After the above publication is created the relations present in this
schema will be invalidated. Now we will check the publication actions
again in CheckCmdReplicaIdentity and the update will fail.
update sch1.t1 set c1 = 11;
ERROR: cannot update table "t1" because it does not have a replica
identity and publishes updates
HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
Okay, I got it but let's add few comments in the code related to it.
Also, I noticed that the code in InvalidatePublicationRels() already
exists in AlterPublicationOptions(). You can try to refactor the
existing code as a separate initial patch.
BTW, I noticed that "for all tables", we don't register invalidations
in the above scenario, and then later that causes conflict on the
subscriber. I think that is a bug in the current code and we can deal
with that separately.
--
With Regards,
Amit Kapila.
On Friday, August 27, 2021 2:13 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.
Thoughts?
Thanks for your new patch. Here are some suggestions:
1.
If a publication published a table and the schema where the table belonged to, the
publication name would show twice when using '\d+' for the table.
Maybe we should add some check to avoid the duplication. Thought?
For example:
create schema sch1;
create table sch1.tbl(a int);
create publication pub for table sch1.tbl, all tables in schema sch1;
postgres=# \d+ sch1.tbl
Table "sch1.tbl"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
a | integer | | | | plain | | |
Publications:
"pub"
"pub"
Access method: heap
2. doc/src/sgml/catalogs.sgml
@@ -6169,6 +6174,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
It seems that we don't need this change.
3. src/bin/psql/tab-complete.c
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
The comment should be updated to "FOR ALL TABLES IN SCHEMA".
Regards
Tang
On Fri, Aug 27, 2021 at 4:13 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.
I notice that "CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
TABLE sc1.test;" maintains the table separately and results in the
following in the \dRp+ output:
Tables:
"sc1.test"
Schemas:
"sc1"
and also then "ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;"
still leaves the "sc1.test" table in the publication.
Is there a reason why we don't/can't support "ALTER SUBSCRIPTION ...
SET ALL TABLES;"?
(I know it wasn't supported before, but now "ALTER SUBSCRIPTION ...
SET ALL TABLES IN SCHEMA ..." is being supported)
I notice that the v22-0003 documentation updates for ALTER
SUBSCRIPTION are missing - but you're probably waiting on all feedback
before proceeding with that.
Regards,
Greg Nancarrow
Fujitsu Australia
On Fri, Aug 27, 2021 at 4:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Aug 27, 2021 at 11:43 AM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Aug 25, 2021 at 3:07 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.Few comments on v22-0001-Added-schema-level-support-for-publication:
========================================================
1. Why in publication_add_schema(), you are registering invalidation
for all schema relations? It seems this is to allow rebuilding the
publication info for decoding sessions. But that is not required as
you are registering rel_sync_cache_publication_cb for
publication_schema relation. In rel_sync_cache_publication_cb, we are
marking replicate_valid as false for each entry which will allow
publication info to be rebuilt in get_rel_sync_entry.I see that it is done for a single relation in the current code in
function publication_add_relation but I guess that is also not
required. You can once test this. If you still think it is required,
can you please explain me why and then we can accordingly add some
comments in the patch.
Added a comment for this.
2. + * Publication object type + */ +typedef enum PublicationObjSpecType +{ + PUBLICATIONOBJ_TABLE, /* Table type */ + PUBLICATIONOBJ_SCHEMA, /* Schema type */ + PUBLICATIONOBJ_SEQUENCE, /* Sequence type */Why add anything related to the sequence in this patch?
This is removed.
3. +get_object_address_publication_schema(List *object, bool missing_ok) +{ + ObjectAddress address; + char *pubname; + Publication *pub; + char *schemaname; + Oid schemaoid; + + ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid); + + /* Fetch schema name and publication name from input list */ + schemaname = strVal(linitial(object)); + pubname = strVal(lsecond(object)); + + schemaoid = get_namespace_oid(schemaname, false); + + /* Now look up the pg_publication tuple */ + pub = GetPublicationByName(pubname, missing_ok); + if (!pub) + return address;Why the schema name handling is different from publication name? Why
can't we pass missing_ok for schema api and handle it similar
publication api?
Modified to handle it similarly.
4. In getPublicationSchemaInfo(), why the missing_ok flag is not used
in get_publication_name() whereas it is used for all other syscache
searches in that function?
Modified it to use missing_ok.
5. Don't we need to expose a view for publication schemas similar to
pg_publication_tables?
I have not handled in this version, I will do it in the next version.
6. publication_add_schema() { .. + /* Can't be system namespace */ + if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" is a system schema", + get_namespace_name(schemaoid)), + errdetail("System schemas cannot be added to publications."))); + + /* Can't be temporary namespace */ + if (isAnyTempNamespace(schemaoid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" is a temporary schema", + get_namespace_name(schemaoid)), + errdetail("Temporary schemas cannot be added to publications."))); .. }Can we change the first detail message as: "This operation is not
supported for system schemas." and the second detail message as:
"Temporary schemas cannot be replicated."? This is to make these
messages similar to corresponding messages for relations in function
check_publication_add_relation(). Can we move these checks to a
separate function?
Modified.
Attached v23 patch has the fixes for the same.
Regards,
Vignesh
Attachments:
v23-0001-Made-the-existing-relation-cache-invalidation-co.patchtext/x-patch; charset=US-ASCII; name=v23-0001-Made-the-existing-relation-cache-invalidation-co.patchDownload
From ad4e3656dcc4b7e480f128bd181631ac645e50ef Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 17:20:53 +0530
Subject: [PATCH v23 1/4] Made the existing relation cache invalidation code
into a function.
Made the existing relation cache invalidation code into a function. This
will be used in the later "FOR ALL TABLES IN SCHEMA" publication for
invalidating the relations.
---
src/backend/commands/publicationcmds.c | 21 +--------------------
src/include/commands/publicationcmds.h | 25 +++++++++++++++++++++++++
2 files changed, 26 insertions(+), 20 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..103b6d09d7 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -45,9 +45,6 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
@@ -324,23 +321,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
List *relids = GetPublicationRelations(pubform->oid,
PUBLICATION_PART_ALL);
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
-
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..d799bc436d 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -17,6 +17,10 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
@@ -25,4 +29,25 @@ extern void RemovePublicationRelById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+/*
+ * Invalidate the relations.
+ */
+static inline void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point it's
+ * cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
#endif /* PUBLICATIONCMDS_H */
--
2.30.2
v23-0002-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v23-0002-Added-schema-level-support-for-publication.patchDownload
From 6f2b7b4965947b7d48eaa9f97400f4d6618b62f8 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 17:30:55 +0530
Subject: [PATCH v23 2/4] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 ++++++++
src/backend/catalog/pg_publication.c | 341 ++++++++++++++++--
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 378 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 5 +-
src/backend/nodes/equalfuncs.c | 5 +-
src/backend/parser/gram.y | 193 ++++++++--
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 4 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++-
src/bin/pg_dump/pg_dump.h | 15 +
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 189 ++++++++--
src/bin/psql/tab-complete.c | 25 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 8 +
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 48 ++-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 6 +-
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 7 +
34 files changed, 1524 insertions(+), 127 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9882e549c4..94ab2a32ba 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -829,6 +830,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1125,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of
+ * the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaoid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2847,6 +2903,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ *pubname = get_publication_name(psform->pspubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(psform->psnspcid);
+ if (!(*nspname))
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3902,6 +4007,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4476,6 +4597,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5711,6 +5836,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..81b0db3f86 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -75,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaoid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -214,6 +240,84 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaoid);
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ schemaRels = GetSchemaPublicationRelations(schemaoid, PUBLICATION_PART_ALL);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -238,10 +342,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,36 +411,79 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
+ systable_endscan(scan);
+ table_close(pubrelsrel, AccessShareLock);
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
+ return result;
+}
+
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
}
systable_endscan(scan);
- table_close(pubrelsrel, AccessShareLock);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of FOR ALL TABLES IN SCHEMA publication oids associated with a
+ * specified schema oid
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONSCHEMAMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_schema) GETSTRUCT(tup))->pspubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
return result;
}
@@ -342,7 +526,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -404,6 +588,97 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets list of relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaOid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(schemaOid != InvalidOid);
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ /* Skip the relations which are not publishable */
+ if (!is_publishable_class(relForm->oid, relForm))
+ continue;
+
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaOid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -533,10 +808,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..35f47d3253 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 103b6d09d7..438bfc4b02 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -34,12 +36,12 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -50,6 +52,10 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +141,145 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ list_free(search_path);
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ConvertPubObjSpecListToOidList(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_UNKNOWN;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ RangeVar *rel;
+ char *relname = strVal(linitial(pubobj->name));
+
+ if (list_length(pubobj->name) == 1 &&
+ (strcmp(relname, "CURRENT_SCHEMA") == 0))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid relation name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ rel = makeRangeVarFromNameList(pubobj->name);
+ rel->inh = pubobj->inh;
+ rel->location = pubobj->location;
+ *rels = lappend(*rels, rel);
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_SCHEMA)
+ {
+ Oid schemaoid;
+ char *schemaname;
+
+ if (list_length(pubobj->name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(pubobj->name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ if (pubobj->spl_rel_type_syn)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(linitial(pubobj->name));
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+ char *nspname;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ list_free(search_path);
+ }
+ else
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaoid);
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +297,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaoidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,19 +368,40 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ConvertPubObjSpecListToOidList(stmt->pubobjects, pstate, &relations,
+ &schemaoidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- table_close(rel, RowExclusiveLock);
+ if (schemaoidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+ Assert(list_length(schemaoidlist) > 0);
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of create
+ * publication command.
+ */
+ LockSchemaList(schemaoidlist);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ }
+
+ table_close(rel, RowExclusiveLock);
InvokeObjectPostCreateHook(PublicationRelationId, puboid, 0);
if (wal_level != WAL_LEVEL_LOGICAL)
@@ -313,13 +481,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -348,15 +522,15 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ errdetail("Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
- if (stmt->tableAction == DEFELEM_ADD)
+ if (stmt->action == DEFELEM_ADD)
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -407,11 +581,70 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set the schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
+{
+ List *schemaoidlist = NIL;
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Schemas cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /* Convert the text list into oid list */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaoidlist);
+ if (stmt->action == DEFELEM_ADD)
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaoidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -440,6 +673,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup);
else
AlterPublicationTables(stmt, rel, tup);
@@ -478,6 +713,58 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_schema pubsch;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ schemaRels = GetSchemaPublicationRelations(pubsch->psnspcid,
+ PUBLICATION_PART_ALL);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaoid, 0, AccessShareLock);
+ }
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -612,6 +899,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -646,6 +966,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("schema \"%s\" is not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..3e57a152f4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12253,6 +12253,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..bcb937c7be 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4808,7 +4808,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4822,8 +4822,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(schemas);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..05ca195af8 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2295,7 +2295,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2308,8 +2308,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(schemas);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 39a2849eba..e408acfce3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +429,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,7 +556,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
-
+%type <schemaspec> SchemaSpec
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,45 +9595,135 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES IN SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+pubobj_expr:
+ any_name
+ {
+ /* inheritance query, implicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = false;
+ $$ = n;
+ }
+ | any_name '*'
+ {
+ /* inheritance query, explicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY any_name
+ {
+ /* no inheritance */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $2;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY '(' any_name ')'
{
- $$ = (Node *) $3;
+ /* no inheritance, SQL99-style syntax */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $3;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
}
- | FOR ALL TABLES
+ | CURRENT_SCHEMA
{
- $$ = (Node *) makeInteger(true);
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = list_make1(makeString("CURRENT_SCHEMA"));
+ n->inh = false;
+ n->spl_rel_type_syn = false;
+ $$ = n;
}
;
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_UNKNOWN;
+ $$->location = @1;
+ }
+ ;
+
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
+ ;
+
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9641,6 +9735,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET ALL TABLES IN SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9656,7 +9755,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
| ALTER PUBLICATION name SET TABLE relation_expr_list
@@ -9664,7 +9763,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
| ALTER PUBLICATION name DROP TABLE relation_expr_list
@@ -9672,7 +9771,31 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->action = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name ADD_P ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -16621,6 +16744,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..1d80bb0fef 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..c83d6421a2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5479,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..3010485f47 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..8d97b13154 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6adbd20778..bb3f73184d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1630,9 +1630,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3968,21 +3972,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4133,6 +4141,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pspubid;
+ int i_psnspcid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pspubid, psnspcid "
+ "FROM pg_catalog.pg_publication_schema");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pspubid = PQfnumber(res, "pspubid");
+ i_psnspcid = PQfnumber(res, "psnspcid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, i, i_pspubid));
+ Oid psnspcid = atooid(PQgetvalue(res, i, i_psnspcid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(psnspcid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4220,6 +4316,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10486,6 +10620,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18741,6 +18878,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..17ca7aeb7b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -629,6 +631,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -735,6 +748,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..13a6fcd660 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 30fb17123e..af88c093be 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,12 +6407,9 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
printTableContent cont;
@@ -6328,6 +6445,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6455,19 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Schemas:", true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6480,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7c6af435a9..3f171233f1 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2680,15 +2689,23 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>, ..." */
else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN
+ * SCHEMA <schema>, ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* CREATE RULE */
/* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..508b663639 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -106,10 +106,18 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaOid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index d799bc436d..9cc74a0d19 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -25,6 +25,7 @@
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6a4d82f0a8..af82a2fd7f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -482,7 +482,9 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7af13dee43..495f7453ea 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,45 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* type of this schemaspec */
+ char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_SCHEMA, /* Schema type */
+ PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ List *name; /* publication object name */
+ bool inh; /* expand rel by inheritance? recursively act
+ * on children? */
+ bool spl_rel_type_syn; /* true if it is special relation type
+ * syntax */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1844,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3630,7 +3670,7 @@ typedef struct CreatePublicationStmt
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3642,10 +3682,12 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
+ /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE/SCHEMA */
List *tables; /* List of tables to add/drop */
+ List *schemas; /* List of schemas to add/drop/set */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..23131de23f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -60,15 +60,15 @@ CREATE TABLE testpub_tbl2 (id serial primary key, data text);
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
-- fail - can't drop from all tables publication
ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..3b4f62025f 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -141,6 +141,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f31a1e4e1e..fc0deec206 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -779,6 +779,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -835,6 +836,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,8 +2047,11 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2331,6 +2336,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v23-0003-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v23-0003-Tests-and-documentation-for-schema-level-support.patchDownload
From 423db53f5d8ca3873b873cfdcbb2e54f67e64f94 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 16:31:14 +0530
Subject: [PATCH v23 3/4] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 72 ++-
doc/src/sgml/ref/alter_publication.sgml | 47 +-
doc/src/sgml/ref/create_publication.sgml | 54 +-
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 468 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 211 +++++++-
.../t/025_rep_changes_for_schema.pl | 168 +++++++
9 files changed, 1042 insertions(+), 17 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..2186a27e27 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..21788f7176 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,18 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -63,6 +77,8 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
+ The <literal>ADD SCHEMA</literal> and <literal>SET SCHEMA</literal> to a
+ publication requires the invoking user to be a superuser.
To alter the owner, you must also be a direct or indirect member of the new
owning role. The new owner must have <literal>CREATE</literal> privilege on
the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
@@ -97,6 +113,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +166,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..db8d90cd1d 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | [ FOR { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ], }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ] } ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,16 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for the all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +164,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -170,9 +182,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ and <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +235,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a4ee54d516..f19f279d55 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2301,6 +2301,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2337,6 +2346,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..bd60f72f78 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 23131de23f..17a42d5ea7 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,92 @@ DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publi
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Schemas cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables:
+ "public.testpub_tbl1"
+Schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to 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
+Tables:
+ "pub_test.testpub_nopk"
+Schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+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
+Schemas:
+ "pub_test"
+
+-- 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
+Tables:
+ "pub_test.testpub_nopk"
+Schemas:
+ "pub_test"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +180,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -246,18 +332,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +378,386 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+ERROR: schema "pub_test2" is 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
+Schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+Tables:
+ "pub_test2.tbl1"
+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
+Tables:
+ "pub_test2.tbl1"
+Schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..49822d7ed4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..cae96e7662 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -133,9 +166,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -143,12 +178,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +204,183 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v23-0004-Alter-publication-syntax-enhancement-to-keep-it-.patchtext/x-patch; charset=US-ASCII; name=v23-0004-Alter-publication-syntax-enhancement-to-keep-it-.patchDownload
From 1bf7d13b1b835f6658695999bd10b23496c177b8 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 19:23:27 +0530
Subject: [PATCH v23 4/4] Alter publication syntax enhancement to keep it
similar to create publication.
Alter publication syntax enhancement to keep it similar to create
publication to support FOR TABLE, FOR ALL TABLES IN SCHEMA syntax.
---
doc/src/sgml/ref/alter_publication.sgml | 26 +++++++--
src/backend/commands/publicationcmds.c | 70 ++++++-----------------
src/backend/nodes/copyfuncs.c | 3 +-
src/backend/nodes/equalfuncs.c | 3 +-
src/backend/parser/gram.y | 75 +++----------------------
src/include/nodes/nodes.h | 1 -
src/include/nodes/parsenodes.h | 20 +------
src/tools/pgindent/typedefs.list | 2 -
8 files changed, 46 insertions(+), 154 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 21788f7176..7722550f19 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,12 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -175,6 +175,20 @@ ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sal
</programlisting>
</para>
+ <para>
+ ADD some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
<para>
Drop some schema from the publication:
<programlisting>
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 438bfc4b02..c26261f9ab 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -141,49 +141,6 @@ parse_publication_options(ParseState *pstate,
}
}
-/*
- * Convert the SchemaSpec list into an Oid list.
- */
-static List *
-ConvertSchemaSpecListToOidList(List *schemas)
-{
- List *schemaoidlist = NIL;
- ListCell *cell;
-
- foreach(cell, schemas)
- {
- SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
- Oid schemaoid;
- List *search_path;
- char *nspname;
-
- if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
- {
- search_path = fetch_search_path(false);
- if (search_path == NIL) /* nothing valid in search_path? */
- ereport(ERROR,
- errcode(ERRCODE_UNDEFINED_SCHEMA),
- errmsg("no schema has been selected"));
-
- schemaoid = linitial_oid(search_path);
- nspname = get_namespace_name(schemaoid);
- if (nspname == NULL) /* recently-deleted namespace? */
- ereport(ERROR,
- errcode(ERRCODE_UNDEFINED_SCHEMA),
- errmsg("no schema has been selected"));
-
- list_free(search_path);
- }
- else
- schemaoid = get_namespace_oid(schema->schemaname, false);
-
- /* Filter out duplicates if user specifies "sch1, sch1" */
- schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
- }
-
- return schemaoidlist;
-}
-
/*
* Convert the PublicationObjSpecType list into schema oid list and rangevar
* list.
@@ -510,7 +467,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
*/
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+ HeapTuple tup, List *tables)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
@@ -524,9 +481,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(tables) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(tables);
if (stmt->action == DEFELEM_ADD)
PublicationAddTables(pubid, rels, false, stmt);
@@ -587,9 +544,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
* Add/Remove/Set the schemas to/from publication.
*/
static void
-AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, List *schemaoidlist)
{
- List *schemaoidlist = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Check that user is allowed to manipulate the publication tables */
@@ -606,9 +563,6 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to add or set schemas")));
- /* Convert the text list into oid list */
- schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
-
/*
* Schema lock is held until the publication is altered to prevent
* concurrent schema deletion. No need to unlock the schemas, the locks
@@ -652,6 +606,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
Relation rel;
HeapTuple tup;
Form_pg_publication pubform;
+ List *relations = NIL;
+ List *schemaoidlist = NIL;
rel = table_open(PublicationRelationId, RowExclusiveLock);
@@ -671,12 +627,18 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
stmt->pubname);
+ ConvertPubObjSpecListToOidList(stmt->pubobjects, pstate, &relations,
+ &schemaoidlist);
+
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
- else if (stmt->schemas)
- AlterPublicationSchemas(stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ if (schemaoidlist)
+ AlterPublicationSchemas(stmt, rel, tup, schemaoidlist);
+ if (relations)
+ AlterPublicationTables(stmt, rel, tup, relations);
+ }
/* Cleanup. */
heap_freetuple(tup);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bcb937c7be..4f15214d8c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4821,8 +4821,7 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
- COPY_NODE_FIELD(schemas);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
COPY_SCALAR_FIELD(action);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 05ca195af8..14af6b9937 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2307,8 +2307,7 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
- COMPARE_NODE_FIELD(schemas);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
COMPARE_SCALAR_FIELD(action);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e408acfce3..53b5d012d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,7 +169,6 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
-static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -258,7 +257,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
- SchemaSpec *schemaspec;
PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
@@ -429,7 +427,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list schema_list pub_obj_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -556,7 +554,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
-%type <schemaspec> SchemaSpec
%type <publicationobjectspec> PublicationObjSpec
%type <publicationobjectspec> pubobj_expr
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -9705,26 +9702,6 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-/* Schema specifications */
-SchemaSpec: ColId
- {
- SchemaSpec *n;
- n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
- n->schemaname = pstrdup($1);
- $$ = n;
- }
- | CURRENT_SCHEMA
- {
- $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
- }
- ;
-
-schema_list: SchemaSpec
- { $$ = list_make1($1); }
- | schema_list ',' SchemaSpec
- { $$ = lappend($1, $3); }
- ;
-
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
@@ -9750,51 +9727,27 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE relation_expr_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
+ n->pubobjects = $5;
n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE relation_expr_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
+ n->pubobjects = $5;
n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE relation_expr_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->action = DEFELEM_DROP;
- $$ = (Node *)n;
- }
- | ALTER PUBLICATION name ADD_P ALL TABLES IN_P SCHEMA schema_list
- {
- AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
- n->pubname = $3;
- n->schemas = $9;
- n->action = DEFELEM_ADD;
- $$ = (Node *)n;
- }
- | ALTER PUBLICATION name SET ALL TABLES IN_P SCHEMA schema_list
- {
- AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
- n->pubname = $3;
- n->schemas = $9;
- n->action = DEFELEM_SET;
- $$ = (Node *)n;
- }
- | ALTER PUBLICATION name DROP ALL TABLES IN_P SCHEMA schema_list
- {
- AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
- n->pubname = $3;
- n->schemas = $9;
+ n->pubobjects = $5;
n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
@@ -16744,20 +16697,6 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
-/*
- * makeSchemaSpec - Create a SchemaSpec with the given type and location
- */
-static SchemaSpec *
-makeSchemaSpec(SchemaSpecType type, int location)
-{
- SchemaSpec *spec = makeNode(SchemaSpec);
-
- spec->schematype = type;
- spec->location = location;
-
- return spec;
-}
-
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index af82a2fd7f..bec18e978a 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,7 +484,6 @@ typedef enum NodeTag
T_CommonTableExpr,
T_PublicationObjSpec,
T_RoleSpec,
- T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 495f7453ea..a2d3990054 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,23 +341,6 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
-/*
- * SchemaSpec - a schema name or CURRENT_SCHEMA
- */
-typedef enum SchemaSpecType
-{
- SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
- SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
-} SchemaSpecType;
-
-typedef struct SchemaSpec
-{
- NodeTag type;
- SchemaSpecType schematype; /* type of this schemaspec */
- char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
- int location; /* token location, or -1 if unknown */
-} SchemaSpec;
-
/*
* Publication object type
*/
@@ -3683,8 +3666,7 @@ typedef struct AlterPublicationStmt
List *options; /* List of DefElem nodes */
/* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE/SCHEMA */
- List *tables; /* List of tables to add/drop */
- List *schemas; /* List of schemas to add/drop/set */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction action; /* What action to perform with the
* tables/schemas */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index fc0deec206..2d24e72329 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2336,8 +2336,6 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
-SchemaSpec
-SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
On Mon, Aug 30, 2021 at 8:23 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Aug 27, 2021 at 4:13 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.Just experimenting with the new syntax so far, and seeing some new
messages and docs, I have the following suggestions for improvements:src/backend/commands/publicationcmds.c
(1)
BEFORE:
for table/for all tables in schema should be specified before the object
AFTER:
FOR TABLE / FOR ALL TABLES IN SCHEMA should be specified before the
table/schema name(s)
Modified it to "FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified
before the table/schema name(s)", I felt we need not have space before and
after "/", I had seen it is specified similarly in few other places.
(2)
BEFORE:
Tables cannot be added, dropped or set on FOR ALL TABLES publications.
AFTER:
Tables cannot be added to, dropped from, or set on FOR ALL TABLES
publications.
Modified
(3)
BEFORE:
Schemas cannot be added, dropped or set on FOR ALL TABLES publications.
AFTER:
Schemas cannot be added to, dropped from, or set on FOR ALL TABLES
publications.
Modified
v22-0002
doc/src/sgml/ref/create_publication.sgml
(1)
BEFORE:
+ Create a publication that publishes all changes for users and
departments
+ table and that publishes all changes for all the tables present in the AFTER: + Create a publication that publishes all changes for tables "users" and + "departments" and that publishes all changes for all the tables present in the
Modified.
I have fixed these comments as part of v23 patch attached at [1]/messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com
/messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com
Regards,
Vignesh
On Mon, Aug 30, 2021 at 9:10 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Friday, August 27, 2021 2:13 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to reduce the
testing effort and also it will be easier if someone disagrees with the syntax. I
will merge it to the main patch later based on the feedback. Attached v22 patch
has the changes for the same.
Thoughts?Hi,
Here are some comments for the new version patches.
About 0001
1)
+ rel->relpersistence = RELPERSISTENCE_PERMANENT;It seems we don't need to set this since makeRangeVarFromNameList()
already set it.
Modified
2) + if (!relids || !schemarelids) + tables = list_concat(relids, schemarelids); + else + tables = list_concat_unique_oid(relids, schemarelids); + }It seems we can simplify the above code like the following:
tables = list_concat_unique_oid(relids, schemarelids);
Modified
3) + relids = GetPublicationRelations(pubform->oid, + PUBLICATION_PART_ALL); + schemarelids = GetAllSchemasPublicationRelations(pubform->oid, + PUBLICATION_PART_ALL); + relids = list_concat(relids, schemarelids);should we invoke list_concat_unique_oid here ?
Modified
4)
+ search_path = fetch_search_path(false); + if (search_path == NIL) /* nothing valid in search_path? */It might be better to list_free(search_path) when not used.
Modified
5) + if (list_length(pubobj->name) == 1 && + (strcmp(relname, "CURRENT_SCHEMA") == 0)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid relation name at or near"), + parser_errposition(pstate, pubobj->location));Maybe we don't need this check, because it will report an error in
OpenTableList() anyway, "relation "CURRENT_SCHEMA" does not exist" , and that
message seems readable to me.
Allowing CURRENT_SCHEMA is required to support current schema for
schema publications, currently I'm allowing this syntax during parsing
and this error is thrown for relations later, this is done to keep the
similar error as earlier before this feature support. I felt we can
keep it like this to maintain the similar error. Thoughts?
About 0002
6)diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl index 0c84d87873..0a479dfe36 100644 --- a/src/test/subscription/t/001_rep_changes.pl +++ b/src/test/subscription/t/001_rep_changes.pl @@ -6,7 +6,7 @@ use strict; use warnings; use PostgresNode; use TestLib; -use Test::More tests => 32; +use Test::More tests => 46;I think it might be better to move these testcases create a separate perl file.
Modified, added to 025_rep_changes_for_schema.pl
About 0003
7)
The v22-0003 seems simple and can remove lots of code in patch v22-0001, so
maybe we can merge 0001 and 0003 into one patch ?
I agree that the code becomes simpler, it reduces a lot of code. I had
kept it like that as the testing effort might be more and also I was
waiting if there was no objection for that syntax from anyone else. I
will wait for a few more reviews and merge it to 0001 if there are no
objections.
I have fixed these comments as part of v23 patch attached at [1]/messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com
Regards,
Vignesh
On Mon, Aug 30, 2021 at 1:31 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Friday, August 27, 2021 2:13 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.
Thoughts?Thanks for your new patch. Here are some suggestions:
1.
If a publication published a table and the schema where the table belonged to, the
publication name would show twice when using '\d+' for the table.
Maybe we should add some check to avoid the duplication. Thought?For example:
create schema sch1;
create table sch1.tbl(a int);
create publication pub for table sch1.tbl, all tables in schema sch1;postgres=# \d+ sch1.tbl
Table "sch1.tbl"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
a | integer | | | | plain | | |
Publications:
"pub"
"pub"
Access method: heap
Modified
2. doc/src/sgml/catalogs.sgml
@@ -6169,6 +6174,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
publication instead of its own.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>It seems that we don't need this change.
Modified
3. src/bin/psql/tab-complete.c
+ /* Complete "CREATE PUBLICATION <name> FOR SCHEMA <schema>, ..." */ + else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA")) + COMPLETE_WITH_QUERY(Query_for_list_of_schemas + " UNION SELECT 'CURRENT_SCHEMA' " + "UNION SELECT 'WITH ('");The comment should be updated to "FOR ALL TABLES IN SCHEMA".
Modified.
I have fixed these comments as part of v23 patch attached at [1]/messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com
Regards,
Vignesh
On Mon, Aug 30, 2021 at 2:14 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Aug 27, 2021 at 4:13 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.I notice that "CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
TABLE sc1.test;" maintains the table separately and results in the
following in the \dRp+ output:Tables:
"sc1.test"
Schemas:
"sc1"and also then "ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;"
still leaves the "sc1.test" table in the publication.
I had intentionally implemented this way, the reason being it gives
the flexibility to modify the publications based on the way the
publication is created. My idea was that if a user specified a
table/schema of the same schema while creating the publication, the
user should be allowed to drop any of them at any time. In the above
case if we don't maintain the results separately, users will not be
able to drop the table from the publication at a later point of time.
Thoughts?
Is there a reason why we don't/can't support "ALTER SUBSCRIPTION ...
SET ALL TABLES;"?
(I know it wasn't supported before, but now "ALTER SUBSCRIPTION ...
SET ALL TABLES IN SCHEMA ..." is being supported)
I agree this can be implemented with the current design. I felt I can
work on getting the current patches into a committable shape and then
work on this after the current patch series is finished. Thoughts?
I notice that the v22-0003 documentation updates for ALTER
SUBSCRIPTION are missing - but you're probably waiting on all feedback
before proceeding with that.
I have fixed this as part of v23 patch attached at [1]/messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com
Regards,
Vignesh
On Mon, Aug 30, 2021 at 12:12 PM Amit Kapila <amit.kapila16@gmail.com>
wrote:
Okay, I got it but let's add few comments in the code related to it.
Also, I noticed that the code in InvalidatePublicationRels() already
exists in AlterPublicationOptions(). You can try to refactor the
existing code as a separate initial patch.
I have made these changes at the v23 patch attached at [1]/messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com.
BTW, I noticed that "for all tables", we don't register invalidations
in the above scenario, and then later that causes conflict on the
subscriber. I think that is a bug in the current code and we can deal
with that separately.
I agree that the cache invalidation has been missed in case of "for all
tables" publication, I have fixed these and posted a patch for the same at
[2]: /messages/by-id/CALDaNm0zkQznFrxzHBoWZUGsf=nKSxhEZZhZ1eTDWLpFok6zZw@mail.gmail.com
[1]: /messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com
/messages/by-id/CALDaNm0xmqJeQEfV5Wnj2BawM=sdFdfOXz5N+gGG3WB6k9=tdw@mail.gmail.com
[2]: /messages/by-id/CALDaNm0zkQznFrxzHBoWZUGsf=nKSxhEZZhZ1eTDWLpFok6zZw@mail.gmail.com
/messages/by-id/CALDaNm0zkQznFrxzHBoWZUGsf=nKSxhEZZhZ1eTDWLpFok6zZw@mail.gmail.com
Regards,
Vigneshhas
On Tue, Aug 31, 2021 at 1:41 PM vignesh C <vignesh21@gmail.com> wrote:
I notice that "CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
TABLE sc1.test;" maintains the table separately and results in the
following in the \dRp+ output:Tables:
"sc1.test"
Schemas:
"sc1"and also then "ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;"
still leaves the "sc1.test" table in the publication.I had intentionally implemented this way, the reason being it gives
the flexibility to modify the publications based on the way the
publication is created. My idea was that if a user specified a
table/schema of the same schema while creating the publication, the
user should be allowed to drop any of them at any time. In the above
case if we don't maintain the results separately, users will not be
able to drop the table from the publication at a later point of time.
Thoughts?
Hmmm. I'm not sure it should work like that (but maybe I'm wrong -
what do others think???).
I thought that "CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
TABLE sc1.test;" should silently just ignore the "TABLE sc1.test"
part, as that is a table in schema sc1, so it's effectively a
duplicate.
Also, I noticed the following:
postgres=# CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
postgres-# TABLE sc1.test;
CREATE PUBLICATION
postgres=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Tables:
"sc1.test"
Schemas:
"sc1"
postgres=# ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;
ALTER PUBLICATION
postgres=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Tables:
"sc1.test"
postgres=# ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;
ERROR: schema "sc1" is not part of the publication
I think it seems odd to not drop table "sc1.test" from the publication
after "ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;".
Also, after running that command again, it seems odd to report that
schema sc1 is not part of the publication, when there remains one
table from that schema in the publication.
And shouldn't it say "tables from schema ... are not part of the
publication" rather than "schema ... is not part of the publication"?
I think the former is better and more accurate. Schemas can contain
database objects other than tables.
Similarly, I'm also thinking that in the "\dRp+" output, it should say
"Tables from schemas:" instead of "Schemas:".
Regards,
Greg Nancarrow
Fujitsu Australia
On Tue, Aug 31, 2021 at 10:50 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Aug 31, 2021 at 1:41 PM vignesh C <vignesh21@gmail.com> wrote:
I notice that "CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
TABLE sc1.test;" maintains the table separately and results in the
following in the \dRp+ output:Tables:
"sc1.test"
Schemas:
"sc1"and also then "ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;"
still leaves the "sc1.test" table in the publication.I had intentionally implemented this way, the reason being it gives
the flexibility to modify the publications based on the way the
publication is created. My idea was that if a user specified a
table/schema of the same schema while creating the publication, the
user should be allowed to drop any of them at any time. In the above
case if we don't maintain the results separately, users will not be
able to drop the table from the publication at a later point of time.
Thoughts?Hmmm. I'm not sure it should work like that (but maybe I'm wrong -
what do others think???).
I thought that "CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
TABLE sc1.test;" should silently just ignore the "TABLE sc1.test"
part, as that is a table in schema sc1, so it's effectively a
duplicate.
I find the way it is implemented to be more intuitive as that gives
users more flexibility to retain certain tables from the schema and
appears to be exactly what users intended by the command. I don't
think finding duplicates among different object lists (schema, table)
is a good idea because tomorrow for some other objects the same thing
can happen. It might be better to get some other opinions on this
matter though.
Also, I noticed the following:
postgres=# CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
postgres-# TABLE sc1.test;
CREATE PUBLICATION
postgres=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Tables:
"sc1.test"
Schemas:
"sc1"postgres=# ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;
ALTER PUBLICATION
postgres=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Tables:
"sc1.test"postgres=# ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;
ERROR: schema "sc1" is not part of the publication
What will happen if you second time run the command as ALTER
PUBLICATION pub1 DROP Table sc1.test? If that works, I think the
behavior should be fine.
--
With Regards,
Amit Kapila.
On Tue, Aug 31, 2021 at 9:15 AM vignesh C <vignesh21@gmail.com> wrote:
On Mon, Aug 30, 2021 at 12:12 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
Okay, I got it but let's add few comments in the code related to it.
Also, I noticed that the code in InvalidatePublicationRels() already
exists in AlterPublicationOptions(). You can try to refactor the
existing code as a separate initial patch.I have made these changes at the v23 patch attached at [1].
*
+/*
+ * Invalidate the relations.
+ */
+static inline void
+InvalidatePublicationRels(List *relids)
I don't see the need to make this an inline function.
* On similar lines, the code in function
GetPubPartitionOptionRelations seems to be extracted from existing
function GetPublicationRelations(). Can't we move it into the 0001
patch?
--
With Regards,
Amit Kapila.
On Tue, Aug 31, 2021 at 4:27 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Aug 31, 2021 at 10:50 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Aug 31, 2021 at 1:41 PM vignesh C <vignesh21@gmail.com> wrote:
I notice that "CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
TABLE sc1.test;" maintains the table separately and results in the
following in the \dRp+ output:Tables:
"sc1.test"
Schemas:
"sc1"and also then "ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;"
still leaves the "sc1.test" table in the publication.I had intentionally implemented this way, the reason being it gives
the flexibility to modify the publications based on the way the
publication is created. My idea was that if a user specified a
table/schema of the same schema while creating the publication, the
user should be allowed to drop any of them at any time. In the above
case if we don't maintain the results separately, users will not be
able to drop the table from the publication at a later point of time.
Thoughts?Hmmm. I'm not sure it should work like that (but maybe I'm wrong -
what do others think???).
I thought that "CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
TABLE sc1.test;" should silently just ignore the "TABLE sc1.test"
part, as that is a table in schema sc1, so it's effectively a
duplicate.I find the way it is implemented to be more intuitive as that gives
users more flexibility to retain certain tables from the schema and
appears to be exactly what users intended by the command. I don't
think finding duplicates among different object lists (schema, table)
is a good idea because tomorrow for some other objects the same thing
can happen. It might be better to get some other opinions on this
matter though.Also, I noticed the following:
postgres=# CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
postgres-# TABLE sc1.test;
CREATE PUBLICATION
postgres=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Tables:
"sc1.test"
Schemas:
"sc1"postgres=# ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;
ALTER PUBLICATION
postgres=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Tables:
"sc1.test"postgres=# ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;
ERROR: schema "sc1" is not part of the publicationWhat will happen if you second time run the command as ALTER
PUBLICATION pub1 DROP Table sc1.test? If that works, I think the
behavior should be fine.
Alter publication drop table works fine:
postgres=# ALTER PUBLICATION pub1 DROP table sc1.test ;
ALTER PUBLICATION
postgres=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------+------------+---------+---------+---------+-----------+----------
vignesh | f | t | t | t | t | f
(1 row)
Regards,
Vignesh
From Mon, Aug 30, 2021 11:26 PM vignesh C <vignesh21@gmail.com> wrote:
On Mon, Aug 30, 2021 at 9:10 AM houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com> wrote:
5) + if (list_length(pubobj->name) == 1 && + (strcmp(relname, "CURRENT_SCHEMA") ==0))
+ ereport(ERROR, +errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid relation
name at or near"),
+ + parser_errposition(pstate, pubobj->location));Maybe we don't need this check, because it will report an error in
OpenTableList() anyway, "relation "CURRENT_SCHEMA" does not exist" ,
and that message seems readable to me.Allowing CURRENT_SCHEMA is required to support current schema for schema
publications, currently I'm allowing this syntax during parsing and this error is
thrown for relations later, this is done to keep the similar error as earlier before
this feature support. I felt we can keep it like this to maintain the similar error.
Thoughts?
Thanks for the explanation, I got the point.
Here are some other comments for v23-000x patches.
1)
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
...
PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
printTableContent cont;
Should we delete the inner declaration of 'title' and 'cont' ?
2)
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
+ /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE/SCHEMA */
SCHEMA => ALL TABLES IN SCHEMA
3)
+ .description = "PUBLICATION SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
Is it better to use something like 'PUBLICATION TABLES IN SCHEMA' to describe
the schema level table publication ? Because there could be some other type
publication such as 'ALL SEQUENCES IN SCHEMA' in the future, it will be better
to make it clear that we only publish table in schema in this patch.
Best regards,
Hou zj
On Tue, Aug 31, 2021 at 8:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
I find the way it is implemented to be more intuitive as that gives
users more flexibility to retain certain tables from the schema and
appears to be exactly what users intended by the command. I don't
think finding duplicates among different object lists (schema, table)
is a good idea because tomorrow for some other objects the same thing
can happen. It might be better to get some other opinions on this
matter though.
I think that such functionality needs to be clearly documented (but
currently the documentation doesn't sufficiently explain it).
Yes, I would definitely like to hear other opinions on this.
Note also that currently parts of the documentation are still
referring to "ADD SCHEMA/DROP SCHEMA/SET SCHEMA" instead of the new
syntax "ADD ALL TABLES IN SCHEMA/DROP ALL TABLES IN SCHEMA/SET ALL
TABLES IN SCHEMA":
e.g.
v23-0003:
doc/src/sgml/ref/alter_publication.sgml
+ The fourth, fifth and sixth variants of this command change which schemas
+ are part of the publication. The <literal>SET SCHEMA</literal> clause will
+ replace the list of schemas in the publication with the specified one.
+ The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses
+ will add and remove one or more schemas from the publication. Note that
+ adding schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
With the new syntax changes (SCHEMA -> ALL TABLES IN SCHEMA), it seems
less intuitive that there are separate schema and table operations on
the publication.
I'd expect a lot of users to naturally think that "ALTER PUBLICATION
pub1 DROP ALL TABLES IN SCHEMA sc1;" would drop from the publication
all tables that are in schema "sc1", which is not what it is currently
doing.
Since the syntax was changed to specifically refer to FOR ALL TABLES
IN SCHEMA rather than FOR SCHEMA, then now it's clear we're referring
to tables only, when specifying "... FOR ALL TABLES in sc1, TABLE
sc1.test", so IMHO it's reasonable to remove duplicates here, rather
than treating these as somehow separate ways of referencing the same
table.
One thing the current scheme doesn't allow is to publish all tables in
a schema except for certain table(s) - and you can't achieve that by
adding all tables from a schema to the publication and then
selectively dropping some of those tables. I thought that this would
be a more common pattern than adding separate tables from schemas that
are already included as part of the publication, in order to "retain"
them if "all tables from schema ..." are later dropped.
postgres=# create schema sc1;
CREATE SCHEMA
postgres=# create table sc1.test(i int);
CREATE TABLE
postgres=# create publication pub1 for all tables in schema sc1;
CREATE PUBLICATION
postgres=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Schemas:
"sc1"
postgres=# alter publication pub1 drop table sc1.test;
ERROR: relation "test" is not part of the publication
The above error message seems slightly misleading (as that table IS
published by that publication) and also note the relation is not
schema-qualified.
Regards,
Greg Nancarrow
Fujitsu Australia
On Monday, August 30, 2021 11:28 PM vignesh C <vignesh21@gmail.com> wrote:
I have fixed these comments as part of v23 patch attached at [1].
[1] - https://www.postgresql.org/message-
id/CALDaNm0xmqJeQEfV5Wnj2BawM%3DsdFdfOXz5N%2BgGG3WB6k9%3Dtdw
%40mail.gmail.com
Thanks for your new patch. Here are some comments on v23 patch.
1. doc/src/sgml/ref/alter_publication.sgml
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
This change seems to be added twice, both 0003 and 0004 patch have this change.
2. src/sgml/ref/create_publication.sgml
There is the following description about "FOR TABLE" parameter:
Only persistent base tables and partitioned tables can be part of a
publication. Temporary tables, unlogged tables, foreign tables,
materialized views, and regular views cannot be part of a publication.
"FOR ALL TABLES IN SCHEMA" parameter also have restrictions, should we add
some doc description for it?
3. When using '\dn+', I noticed that the list of publications only contains the
publications for "SCHEMA", "FOR ALL TABLES" publications are not shown. Is it designed on purpose?
(The result of '\d+' lists the publications of "SCHEAME" and "FOR ALL TABLES").
For example:
create schema sch1;
create table sch1.tbl(a int);
create publication pub_schema for all tables in schema sch1;
create publication pub_all_tables for all tables;
postgres=# \d+ sch1.tbl
Table "sch1.tbl"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
a | integer | | | | plain | | |
Publications:
"pub_all_tables"
"pub_schema"
Access method: heap
postgres=# \dn+ sch1
List of schemas
Name | Owner | Access privileges | Description
------+----------+-------------------+-------------
sch1 | postgres | |
Publications:
"pub_schema"
Regards
Tang
On Wed, Sep 1, 2021 at 8:52 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Aug 31, 2021 at 8:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
I find the way it is implemented to be more intuitive as that gives
users more flexibility to retain certain tables from the schema and
appears to be exactly what users intended by the command. I don't
think finding duplicates among different object lists (schema, table)
is a good idea because tomorrow for some other objects the same thing
can happen. It might be better to get some other opinions on this
matter though.I think that such functionality needs to be clearly documented (but
currently the documentation doesn't sufficiently explain it).
I agree that we should document it clearly if we decide to go in this direction.
Yes, I would definitely like to hear other opinions on this.
Note also that currently parts of the documentation are still
referring to "ADD SCHEMA/DROP SCHEMA/SET SCHEMA" instead of the new
syntax "ADD ALL TABLES IN SCHEMA/DROP ALL TABLES IN SCHEMA/SET ALL
TABLES IN SCHEMA":e.g. v23-0003: doc/src/sgml/ref/alter_publication.sgml + The fourth, fifth and sixth variants of this command change which schemas + are part of the publication. The <literal>SET SCHEMA</literal> clause will + replace the list of schemas in the publication with the specified one. + The <literal>ADD SCHEMA</literal> and <literal>DROP SCHEMA</literal> clauses + will add and remove one or more schemas from the publication. Note that + adding schemas to a publication that is already subscribed to will require + a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on + the subscribing side in order to become effective.With the new syntax changes (SCHEMA -> ALL TABLES IN SCHEMA), it seems
less intuitive that there are separate schema and table operations on
the publication.
I think the documentation could be improved w.r.t above points because
it is quite clear that we support schema and tables separately.
I'd expect a lot of users to naturally think that "ALTER PUBLICATION
pub1 DROP ALL TABLES IN SCHEMA sc1;" would drop from the publication
all tables that are in schema "sc1", which is not what it is currently
doing.
Since the syntax was changed to specifically refer to FOR ALL TABLES
IN SCHEMA rather than FOR SCHEMA, then now it's clear we're referring
to tables only, when specifying "... FOR ALL TABLES in sc1, TABLE
sc1.test", so IMHO it's reasonable to remove duplicates here, rather
than treating these as somehow separate ways of referencing the same
table.
I see your point and if we decide to go this path then it is better to
give an error than silently removing duplicates.
One thing the current scheme doesn't allow is to publish all tables in
a schema except for certain table(s)
True, we can always support that as a separate feature. Note that same
is true for existing FOR ALL TABLES syntax where users could expect to
leave few tables with syntax like FOR ALL TABLES EXCEPT t1,t2,... but
we don't have that yet.
- and you can't achieve that by
adding all tables from a schema to the publication and then
selectively dropping some of those tables. I thought that this would
be a more common pattern than adding separate tables from schemas that
are already included as part of the publication, in order to "retain"
them if "all tables from schema ..." are later dropped.postgres=# create schema sc1;
CREATE SCHEMA
postgres=# create table sc1.test(i int);
CREATE TABLE
postgres=# create publication pub1 for all tables in schema sc1;
CREATE PUBLICATION
postgres=# \dRp+Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Schemas:
"sc1"postgres=# alter publication pub1 drop table sc1.test;
ERROR: relation "test" is not part of the publicationThe above error message seems slightly misleading (as that table IS
published by that publication) and also note the relation is not
schema-qualified.
I think we should try to improve this message, maybe we can give
detail message similar to what we give For ALL Tables case (DETAIL:
Tables that are part of SCHEMA cannot be dropped independently).
--
With Regards,
Amit Kapila.
On Tue, Aug 31, 2021 at 1:41 PM vignesh C <vignesh21@gmail.com> wrote:
On Mon, Aug 30, 2021 at 2:14 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Aug 27, 2021 at 4:13 PM vignesh C <vignesh21@gmail.com> wrote:
I have implemented this in the 0003 patch, I have kept it separate to
reduce the testing effort and also it will be easier if someone
disagrees with the syntax. I will merge it to the main patch later
based on the feedback. Attached v22 patch has the changes for the
same.I notice that "CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1,
TABLE sc1.test;" maintains the table separately and results in the
following in the \dRp+ output:Tables:
"sc1.test"
Schemas:
"sc1"and also then "ALTER PUBLICATION pub1 DROP ALL TABLES IN SCHEMA sc1;"
still leaves the "sc1.test" table in the publication.I had intentionally implemented this way, the reason being it gives
the flexibility to modify the publications based on the way the
publication is created. My idea was that if a user specified a
table/schema of the same schema while creating the publication, the
user should be allowed to drop any of them at any time. In the above
case if we don't maintain the results separately, users will not be
able to drop the table from the publication at a later point of time.
Thoughts?
I think ALL should mean ALL.
When you say CREATE PUBLICATION pub1 FOR ALL TABLES; you know it means
ALL tables;
When you say CREATE PUBLICATION pub1 FOR ALL TABLES IN SCHEMA sc1; you
know it means ALL tables in the schema sc1;
Similarly, when you say ALTER PUBLICATION pub1 DROP ALL TABLES IN
SCHEMA sc1; I would expect it means to drop ALL tables in sc1 - not
all of them sometimes but not all of them at other times or even none
of them.
~~
I thought a motivation for this patch was to make it easy for the user
to specify tables en-masse. e.g, saying FOR ALL TABLES IN SCHEMA sc1
is a convenience instead of having to specify every schema table
explicitly.
e.g. What if there are 100s of tables explicitly in a publication.
1. CREATE PUBLICATION pub FOR TABLE sc1.t1,sc1,t2,sc1.t3,....,sc1.t999;
2. ALTER PUBLICATION pub DROP ALL TABLES IN SCHEMA sc1;
IIUC the current patch behaviour for step 2 will do nothing at all.
That doesn't seem right to me. Where is the user-convenience factor
for dropping tables here?
~~
If the rule was simply "ALL means ALL" that hardly even needs
documentation to describe it. OTOH, current patch behaviour is quirky
wrt how the publication was created and would need to be carefully
documented.
It is easy to imagine a user unfamiliar with how the publication was
originally created will be confused when ALTER PUBLICATION DROP ALL
TABLES IN SCHEMA sc1 still leaves some sc1 tables lurking.
~~
Schema objects are not part of the publication. Current only TABLES
are in publications, so I thought that \dRp+ output would just be the
of "Tables" in the publication. Schemas would not even be displayed at
all (except in the table name).
Consider that everything is just going to get messier when SEQUENCES
are added. If you list Schemas like is done now then what's that going
to look like later? I think you'd need to display many lists like -
"Tables" and "Tables for Schemas" and "Sequences" and "Sequences for
Schema"...
IMO it's all beginning to sound like it would become overly complex
~~
For all the above reasons I think ALL should mean ALL, and also \dRp+
should display tables only. (and later table and sequences only)
YMMV.
------
Kind Regards,
Peter Smith
Fujitsu Australia
On Wed, Sep 1, 2021 at 12:06 PM Peter Smith <smithpb2250@gmail.com> wrote:
Similarly, when you say ALTER PUBLICATION pub1 DROP ALL TABLES IN
SCHEMA sc1; I would expect it means to drop ALL tables in sc1 - not
all of them sometimes but not all of them at other times or even none
of them.~~
I thought a motivation for this patch was to make it easy for the user
to specify tables en-masse. e.g, saying FOR ALL TABLES IN SCHEMA sc1
is a convenience instead of having to specify every schema table
explicitly.e.g. What if there are 100s of tables explicitly in a publication.
1. CREATE PUBLICATION pub FOR TABLE sc1.t1,sc1,t2,sc1.t3,....,sc1.t999;
2. ALTER PUBLICATION pub DROP ALL TABLES IN SCHEMA sc1;IIUC the current patch behaviour for step 2 will do nothing at all.
That doesn't seem right to me. Where is the user-convenience factor
for dropping tables here?
It will give an error (ERROR: schema "sc1" is not part of the
publication), we can probably add DETAIL and HINT message (like try
dropping using DROP TABLE syntax) indicating the reason and what user
need to do in this regard. I think if the user has specified tables
individually instead of ALL TABLES IN SCHEMA, she should drop them
individually rather than expecting them to be dropped via SCHEMA
command. I think in a similar context you can also argue that we
should have DROP ALL TABLES syntax to drop all the tables that were
individually specified by the user with FOR TABLE command.
I think this should be documented as well to avoid any confusion.
~~
If the rule was simply "ALL means ALL" that hardly even needs
documentation to describe it. OTOH, current patch behaviour is quirky
wrt how the publication was created and would need to be carefully
documented.It is easy to imagine a user unfamiliar with how the publication was
originally created will be confused when ALTER PUBLICATION DROP ALL
TABLES IN SCHEMA sc1 still leaves some sc1 tables lurking.~~
Schema objects are not part of the publication. Current only TABLES
are in publications, so I thought that \dRp+ output would just be the
of "Tables" in the publication. Schemas would not even be displayed at
all (except in the table name).
Hmm, I think this will lead to a lot of table names in output. I think
displaying schema names separately is a better approach here.
Consider that everything is just going to get messier when SEQUENCES
are added. If you list Schemas like is done now then what's that going
to look like later? I think you'd need to display many lists like -
"Tables" and "Tables for Schemas" and "Sequences" and "Sequences for
Schema"...
I think that would be better than displaying all the tables and
sequences as that will be very difficult for users to view/read.
--
With Regards,
Amit Kapila.
On Tue, Aug 31, 2021 at 6:28 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
I have made these changes at the v23 patch attached at [1].
* +/* + * Invalidate the relations. + */ +static inline void +InvalidatePublicationRels(List *relids)I don't see the need to make this an inline function.
Modified
* On similar lines, the code in function
GetPubPartitionOptionRelations seems to be extracted from existing
function GetPublicationRelations(). Can't we move it into the 0001
patch?
Modified
Thanks for the comments, the attached v24 patch has the changes for the same.
Regards,
Vignesh
Attachments:
v24-0001-Made-the-existing-relation-cache-invalidation-an.patchtext/x-patch; charset=US-ASCII; name=v24-0001-Made-the-existing-relation-cache-invalidation-an.patchDownload
From 94c0c133b542ea900a0c056b9fb118ff4cfa083f Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 17:20:53 +0530
Subject: [PATCH v24 1/5] Made the existing relation cache invalidation and
getting the relations based on the publication partition option for a
specified relation into a function.
Made the existing relation cache invalidation code into a function. Also
made getting the relations based on the publication partition option for a
specified relation into a function. This will be used in the later
"FOR ALL TABLES IN SCHEMA" implementation patch.
---
src/backend/catalog/pg_publication.c | 67 +++++++++++++++-----------
src/backend/commands/publicationcmds.c | 42 ++++++++--------
src/include/commands/publicationcmds.h | 5 ++
3 files changed, 67 insertions(+), 47 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 2a2fe03c13..49a089fbbb 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -238,10 +238,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,32 +307,8 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
-
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
-
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
-
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
-
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
}
systable_endscan(scan);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 8487eeb7e6..e1d17f6fa6 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -45,9 +45,6 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
@@ -324,23 +321,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
List *relids = GetPublicationRelations(pubform->oid,
PUBLICATION_PART_ALL);
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
-
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
@@ -350,6 +331,27 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
InvokeObjectPostAlterHook(PublicationRelationId, pubform->oid, 0);
}
+/*
+ * Invalidate the relations.
+ */
+void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point it's
+ * cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
/*
* Add or remove table to/from publication.
*/
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..fa47d6d761 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -17,6 +17,10 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
@@ -24,5 +28,6 @@ extern void RemovePublicationRelById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern void InvalidatePublicationRels(List *relids);
#endif /* PUBLICATIONCMDS_H */
--
2.30.2
v24-0002-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v24-0002-Added-schema-level-support-for-publication.patchDownload
From 5cbd8836a12fe5423cd5751fbc0fe8ec4fb2dfab Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Thu, 2 Sep 2021 11:13:46 +0530
Subject: [PATCH v24 2/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_schema" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_schema dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_schema
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 ++++++++
src/backend/catalog/pg_publication.c | 280 ++++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 378 +++++++++++++++++++-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 5 +-
src/backend/nodes/equalfuncs.c | 5 +-
src/backend/parser/gram.y | 193 ++++++++--
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 4 +
src/backend/utils/cache/syscache.c | 23 ++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++-
src/bin/pg_dump/pg_dump.h | 15 +
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 192 +++++++---
src/bin/psql/tab-complete.c | 32 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 8 +
src/include/catalog/pg_publication_schema.h | 47 +++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 48 ++-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 6 +-
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 7 +
34 files changed, 1492 insertions(+), 108 deletions(-)
create mode 100644 src/include/catalog/pg_publication_schema.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..b2ee87b105 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_rel.h pg_publication_schema.h \
+ pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..09d7f1a5ea 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..d974750473 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -180,6 +181,7 @@ static const Oid object_classes[] = {
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
+ PublicationSchemaRelationId, /* OCLASS_PUBLICATION_SCHEMA */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
@@ -1460,6 +1462,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationRelById(object->objectId);
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
@@ -2853,6 +2859,9 @@ getObjectClass(const ObjectAddress *object)
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
+ case PublicationSchemaRelationId:
+ return OCLASS_PUBLICATION_SCHEMA;
+
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index deaabaeae9..f6df10ec0b 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -49,6 +49,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -829,6 +830,10 @@ static const struct object_type_map
{
"publication relation", OBJECT_PUBLICATION_REL
},
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1125,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_SCHEMA:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication tables in schema. The first
+ * element of the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaoid;
+
+ ObjectAddressSet(address, PublicationSchemaRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaoid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaoid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONSCHEMAMAP, Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_schema psform;
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ psform = (Form_pg_publication_schema) GETSTRUCT(tup);
+ *pubname = get_publication_name(psform->pspubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(psform->psnspcid);
+ if (!(*nspname))
+ {
+ Oid psnspcid = psform->psnspcid;
+
+ pfree(pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ psnspcid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3903,6 +4008,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4477,6 +4598,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication relation");
break;
+ case OCLASS_PUBLICATION_SCHEMA:
+ appendStringInfoString(&buffer, "publication schema");
+ break;
+
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
@@ -5712,6 +5837,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_SCHEMA:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 49a089fbbb..81b0db3f86 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -75,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaoid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaoid) || IsToastNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a system schema",
+ get_namespace_name(schemaoid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" is a temporary schema",
+ get_namespace_name(schemaoid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -214,6 +240,84 @@ publication_add_relation(Oid pubid, Relation targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_schema];
+ bool nulls[Natts_pg_publication_schema];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaoid);
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONSCHEMAMAP, ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaoid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationSchemaObjectIndexId,
+ Anum_pg_publication_schema_oid);
+ values[Anum_pg_publication_schema_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_schema_pspubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_schema_psnspcid - 1] =
+ ObjectIdGetDatum(schemaoid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationSchemaRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaoid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ schemaRels = GetSchemaPublicationRelations(schemaoid, PUBLICATION_PART_ALL);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -317,6 +421,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationSchemaRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_schema_pspubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel, PublicationSchemaPsnspcidPspubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_schema pubsch;
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->psnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of FOR ALL TABLES IN SCHEMA publication oids associated with a
+ * specified schema oid
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONSCHEMAMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_schema) GETSTRUCT(tup))->pspubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -355,7 +526,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -417,6 +588,97 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets list of relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaOid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(schemaOid != InvalidOid);
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaOid);
+
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ /* Skip the relations which are not publishable */
+ if (!is_publishable_class(relForm->oid, relForm))
+ continue;
+
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaOid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaOid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -546,10 +808,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..e7c27459d8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -661,6 +661,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/* ignore object types that don't have schema-qualified names */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..35f47d3253 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1051,6 +1052,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
return true;
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index e1d17f6fa6..dd3fb27f28 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,8 +25,10 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -34,12 +36,12 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -50,6 +52,10 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +141,145 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the SchemaSpec list into an Oid list.
+ */
+static List *
+ConvertSchemaSpecListToOidList(List *schemas)
+{
+ List *schemaoidlist = NIL;
+ ListCell *cell;
+
+ foreach(cell, schemas)
+ {
+ SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
+ Oid schemaoid;
+ List *search_path;
+ char *nspname;
+
+ if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
+ {
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ list_free(search_path);
+ }
+ else
+ schemaoid = get_namespace_oid(schema->schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
+ }
+
+ return schemaoidlist;
+}
+
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ConvertPubObjSpecListToOidList(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_UNKNOWN;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ RangeVar *rel;
+ char *relname = strVal(linitial(pubobj->name));
+
+ if (list_length(pubobj->name) == 1 &&
+ (strcmp(relname, "CURRENT_SCHEMA") == 0))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid relation name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ rel = makeRangeVarFromNameList(pubobj->name);
+ rel->inh = pubobj->inh;
+ rel->location = pubobj->location;
+ *rels = lappend(*rels, rel);
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_SCHEMA)
+ {
+ Oid schemaoid;
+ char *schemaname;
+
+ if (list_length(pubobj->name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(pubobj->name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ if (pubobj->spl_rel_type_syn)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(linitial(pubobj->name));
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+ char *nspname;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ list_free(search_path);
+ }
+ else
+ schemaoid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaoid);
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +297,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaoidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,19 +368,40 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ConvertPubObjSpecListToOidList(stmt->pubobjects, pstate, &relations,
+ &schemaoidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- table_close(rel, RowExclusiveLock);
+ if (schemaoidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+ Assert(list_length(schemaoidlist) > 0);
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of create
+ * publication command.
+ */
+ LockSchemaList(schemaoidlist);
+ PublicationAddSchemas(puboid, schemaoidlist, true, NULL);
+ }
+
+ table_close(rel, RowExclusiveLock);
InvokeObjectPostCreateHook(PublicationRelationId, puboid, 0);
if (wal_level != WAL_LEVEL_LOGICAL)
@@ -313,13 +481,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -369,15 +543,15 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ errdetail("Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
Assert(list_length(stmt->tables) > 0);
rels = OpenTableList(stmt->tables);
- if (stmt->tableAction == DEFELEM_ADD)
+ if (stmt->action == DEFELEM_ADD)
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -428,11 +602,70 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
+{
+ List *schemaoidlist = NIL;
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /* Convert the text list into oid list */
+ schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaoidlist);
+ if (stmt->action == DEFELEM_ADD)
+ PublicationAddSchemas(pubform->oid, schemaoidlist, false, stmt);
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaoidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaoidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaoidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -461,6 +694,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
+ else if (stmt->schemas)
+ AlterPublicationSchemas(stmt, rel, tup);
else
AlterPublicationTables(stmt, rel, tup);
@@ -499,6 +734,58 @@ RemovePublicationRelById(Oid proid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_schema pubsch;
+
+ rel = table_open(PublicationSchemaRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONSCHEMA, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_schema) GETSTRUCT(tup);
+ schemaRels = GetSchemaPublicationRelations(pubsch->psnspcid,
+ PUBLICATION_PART_ALL);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaoid, 0, AccessShareLock);
+ }
+}
+
/*
* Open relations specified by a RangeVar list.
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
@@ -633,6 +920,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ /* Must be owner of the schema or superuser */
+ if (!pg_namespace_ownercheck(schemaoid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
+ get_namespace_name(schemaoid));
+
+ obj = publication_add_schema(pubid, schemaoid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationSchemaRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -667,6 +987,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaoid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONSCHEMAMAP,
+ Anum_pg_publication_schema_oid,
+ ObjectIdGetDatum(schemaoid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaoid))));
+ }
+
+ ObjectAddressSet(obj, PublicationSchemaRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..accaf2ed2e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_SCHEMA:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..3e57a152f4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12253,6 +12253,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
+ case OCLASS_PUBLICATION_SCHEMA:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..bcb937c7be 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4808,7 +4808,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4822,8 +4822,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(schemas);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..05ca195af8 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2295,7 +2295,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2308,8 +2308,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(schemas);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 39a2849eba..e408acfce3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,6 +169,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
+static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -257,6 +258,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ SchemaSpec *schemaspec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +429,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list
+ drop_option_list schema_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,7 +556,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
-
+%type <schemaspec> SchemaSpec
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,45 +9595,135 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES IN SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE relation_expr_list
+pubobj_expr:
+ any_name
+ {
+ /* inheritance query, implicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = false;
+ $$ = n;
+ }
+ | any_name '*'
+ {
+ /* inheritance query, explicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY any_name
+ {
+ /* no inheritance */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $2;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY '(' any_name ')'
{
- $$ = (Node *) $3;
+ /* no inheritance, SQL99-style syntax */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $3;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
}
- | FOR ALL TABLES
+ | CURRENT_SCHEMA
{
- $$ = (Node *) makeInteger(true);
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = list_make1(makeString("CURRENT_SCHEMA"));
+ n->inh = false;
+ n->spl_rel_type_syn = false;
+ $$ = n;
}
;
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_UNKNOWN;
+ $$->location = @1;
+ }
+ ;
+
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
+ ;
+
+/* Schema specifications */
+SchemaSpec: ColId
+ {
+ SchemaSpec *n;
+ n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
+ n->schemaname = pstrdup($1);
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
+ }
+ ;
+
+schema_list: SchemaSpec
+ { $$ = list_make1($1); }
+ | schema_list ',' SchemaSpec
+ { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -9641,6 +9735,11 @@ publication_for_tables:
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET ALL TABLES IN SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9656,7 +9755,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
| ALTER PUBLICATION name SET TABLE relation_expr_list
@@ -9664,7 +9763,7 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
| ALTER PUBLICATION name DROP TABLE relation_expr_list
@@ -9672,7 +9771,31 @@ AlterPublicationStmt:
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->action = DEFELEM_DROP;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name ADD_P ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_ADD;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name SET ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_SET;
+ $$ = (Node *)n;
+ }
+ | ALTER PUBLICATION name DROP ALL TABLES IN_P SCHEMA schema_list
+ {
+ AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+ n->pubname = $3;
+ n->schemas = $9;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -16621,6 +16744,20 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
+/*
+ * makeSchemaSpec - Create a SchemaSpec with the given type and location
+ */
+static SchemaSpec *
+makeSchemaSpec(SchemaSpecType type, int location)
+{
+ SchemaSpec *spec = makeNode(SchemaSpec);
+
+ spec->schematype = type;
+ spec->location = location;
+
+ return spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..1d80bb0fef 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONSCHEMAMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..c83d6421a2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5478,6 +5479,9 @@ GetRelationPublicationActions(Relation relation)
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..924b7bcad5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_schema.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
#include "catalog/pg_rewrite.h"
@@ -650,6 +651,28 @@ static const struct cachedesc cacheinfo[] = {
},
64
},
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMA */
+ PublicationSchemaObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_schema_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationSchemaRelationId, /* PUBLICATIONSCHEMAMAP */
+ PublicationSchemaPsnspcidPspubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_schema_psnspcid,
+ Anum_pg_publication_schema_pspubid,
+ 0,
+ 0
+ },
+ 64
+ },
{RangeRelationId, /* RANGEMULTIRANGE */
RangeMultirangeTypidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 67be849829..0e3e3a8392 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1630,9 +1630,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3960,21 +3964,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4125,6 +4133,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pspubid;
+ int i_psnspcid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pspubid, psnspcid "
+ "FROM pg_catalog.pg_publication_schema");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pspubid = PQfnumber(res, "pspubid");
+ i_psnspcid = PQfnumber(res, "psnspcid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pspubid = atooid(PQgetvalue(res, i, i_pspubid));
+ Oid psnspcid = atooid(PQgetvalue(res, i, i_psnspcid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pspubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(psnspcid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4212,6 +4308,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10479,6 +10613,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18734,6 +18871,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..e19e09726e 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..200cc3edb5 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..252624efd8 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_schema ps ON p.oid = ps.pspubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = ps.psnspcid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid AND\n"
+ "p.oid = ps.pspubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6407,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6443,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6453,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_schema ps\n"
+ "WHERE n.oid = ps.psnspcid\n"
+ " AND ps.pspubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6479,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 75b867685a..133a5a73a0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,26 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN
+ * SCHEMA <schema>, ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..e5e88d3a31 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,7 @@ typedef enum ObjectClass
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
+ OCLASS_PUBLICATION_SCHEMA, /* pg_publication_schema */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
} ObjectClass;
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index f332bad4d4..508b663639 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -106,10 +106,18 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaOid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaoid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_schema.h b/src/include/catalog/pg_publication_schema.h
new file mode 100644
index 0000000000..fc50655af1
--- /dev/null
+++ b/src/include/catalog/pg_publication_schema.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_schema.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_schema)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_schema.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_SCHEMA_H
+#define PG_PUBLICATION_SCHEMA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_schema_d.h"
+
+
+/* ----------------
+ * pg_publication_schema definition. cpp turns this into
+ * typedef struct FormData_pg_publication_schema
+ * ----------------
+ */
+CATALOG(pg_publication_schema,8901,PublicationSchemaRelationId)
+{
+ Oid oid; /* oid */
+ Oid pspubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
+
+/* ----------------
+ * Form_pg_publication_schema corresponds to a pointer to a tuple with
+ * the format of pg_publication_schema relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_schema *Form_pg_publication_schema;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_schema_oid_index, 8902, PublicationSchemaObjectIndexId, on pg_publication_schema using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_schema_psnspcid_pspubid_index, 8903, PublicationSchemaPsnspcidPspubidIndexId, on pg_publication_schema using btree(psnspcid oid_ops, pspubid oid_ops));
+
+#endif /* PG_PUBLICATION_SCHEMA_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index fa47d6d761..276f77fa8c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -25,6 +25,7 @@
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6a4d82f0a8..af82a2fd7f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -482,7 +482,9 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
+ T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7af13dee43..70146d1fa5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,45 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * SchemaSpec - a schema name or CURRENT_SCHEMA
+ */
+typedef enum SchemaSpecType
+{
+ SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
+ SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
+} SchemaSpecType;
+
+typedef struct SchemaSpec
+{
+ NodeTag type;
+ SchemaSpecType schematype; /* type of this schemaspec */
+ char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
+ int location; /* token location, or -1 if unknown */
+} SchemaSpec;
+
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_SCHEMA, /* Schema type */
+ PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ List *name; /* publication object name */
+ bool inh; /* expand rel by inheritance? recursively act
+ * on children? */
+ bool spl_rel_type_syn; /* true if it is special relation type
+ * syntax */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1844,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_SCHEMA,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3630,7 +3670,7 @@ typedef struct CreatePublicationStmt
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3642,10 +3682,12 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
List *tables; /* List of tables to add/drop */
+ List *schemas; /* List of schemas to add/drop/set */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..1ba295206a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,8 @@ enum SysCacheIdentifier
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
+ PUBLICATIONSCHEMA,
+ PUBLICATIONSCHEMAMAP,
RANGEMULTIRANGE,
RANGETYPE,
RELNAMENSP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..ddb421c394 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -260,6 +260,8 @@ NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
+NOTICE: checking pg_publication_schema {pspubid} => pg_publication {oid}
+NOTICE: checking pg_publication_schema {psnspcid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..23131de23f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -60,15 +60,15 @@ CREATE TABLE testpub_tbl2 (id serial primary key, data text);
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
-- fail - can't drop from all tables publication
ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..3b4f62025f 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -141,6 +141,7 @@ pg_policy|t
pg_proc|t
pg_publication|t
pg_publication_rel|t
+pg_publication_schema|t
pg_range|t
pg_replication_origin|t
pg_rewrite|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f31a1e4e1e..fc0deec206 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -779,6 +779,7 @@ FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
FormData_pg_publication_rel
+FormData_pg_publication_schema
FormData_pg_range
FormData_pg_replication_origin
FormData_pg_rewrite
@@ -835,6 +836,7 @@ Form_pg_policy
Form_pg_proc
Form_pg_publication
Form_pg_publication_rel
+Form_pg_publication_schema
Form_pg_range
Form_pg_replication_origin
Form_pg_rewrite
@@ -2045,8 +2047,11 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
@@ -2331,6 +2336,8 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
+SchemaSpec
+SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v24-0003-Tests-and-documentation-for-schema-level-support.patchtext/x-patch; charset=US-ASCII; name=v24-0003-Tests-and-documentation-for-schema-level-support.patchDownload
From 270edbd974e689706fcf83fc243853db957f509f Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 16:31:14 +0530
Subject: [PATCH v24 3/5] Tests and documentation for schema level support for
publication.
Tests and documentation for schema level support for publication.
---
doc/src/sgml/catalogs.sgml | 72 ++-
doc/src/sgml/ref/alter_publication.sgml | 50 +-
doc/src/sgml/ref/create_publication.sgml | 61 ++-
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 468 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 211 +++++++-
.../t/025_rep_changes_for_schema.pl | 168 +++++++
9 files changed, 1052 insertions(+), 17 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..2186a27e27 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -245,6 +245,11 @@
<entry>relation to publication mapping</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-schema"><structname>pg_publication_schema</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-schema">
+ <title><structname>pg_publication_schema</structname></title>
+
+ <indexterm zone="catalog-pg-publication-schema">
+ <primary>pg_publication_schema</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_schema</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_schema</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..499bd8e5c0 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -24,6 +24,9 @@ PostgreSQL documentation
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,21 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which all tables
+ in schema are part of the publication. The
+ <literal>SET ALL TABLES IN SCHEMA</literal> clause will replace the list of
+ all tables in schemas of the publication with the specified one. The
+ <literal>ADD ALL TABLES IN SCHEMA</literal> clause will add the list of all
+ tables in schemas to the publication and
+ <literal>DROP ALL TABLES IN SCHEMA</literal> clause will remove the list of
+ all tables in schemas from the publication. Note that adding
+ schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -63,6 +80,8 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
+ The <literal>ADD SCHEMA</literal> and <literal>SET SCHEMA</literal> to a
+ publication requires the invoking user to be a superuser.
To alter the owner, you must also be a direct or indirect member of the new
owning role. The new owner must have <literal>CREATE</literal> privilege on
the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
@@ -97,6 +116,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +169,26 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..e6720325b7 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | [ FOR { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ], }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ] } ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +100,23 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +171,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -170,9 +189,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ and <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +242,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index be1f3a5175..f838bf638b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2301,6 +2301,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2337,6 +2346,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..bd60f72f78 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -428,6 +429,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication schema | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 23131de23f..cfb1d5bf73 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,92 @@ DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publi
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to 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
+Tables:
+ "pub_test.testpub_nopk"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+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
+Tables from schemas:
+ "pub_test"
+
+-- 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
+Tables:
+ "pub_test.testpub_nopk"
+Tables from schemas:
+ "pub_test"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +180,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -246,18 +332,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +378,386 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: "pg_catalog" is a system schema
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..49822d7ed4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -198,6 +199,7 @@ WITH objects (type, name, args) AS (VALUES
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
+ ('publication schema', '{addr_nsp}', '{addr_pub_schema}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
)
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..cae96e7662 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -133,9 +166,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -143,12 +178,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +204,183 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_schema ps WHERE n.oid = ps.psnspcid AND p.oid = ps.pspubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v24-0004-Alter-publication-syntax-enhancement-to-keep-it-.patchtext/x-patch; charset=US-ASCII; name=v24-0004-Alter-publication-syntax-enhancement-to-keep-it-.patchDownload
From af7caa9f1e7ad12c501bb481a52bba9cfbe71d33 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Wed, 1 Sep 2021 09:31:21 +0530
Subject: [PATCH v24 4/5] Alter publication syntax enhancement to keep it
similar to create publication.
Alter publication syntax enhancement to keep it similar to create
publication to support FOR TABLE, FOR ALL TABLES IN SCHEMA syntax.
---
doc/src/sgml/ref/alter_publication.sgml | 19 +++++--
src/backend/commands/publicationcmds.c | 70 ++++++-----------------
src/backend/nodes/copyfuncs.c | 3 +-
src/backend/nodes/equalfuncs.c | 3 +-
src/backend/parser/gram.y | 75 +++----------------------
src/include/nodes/nodes.h | 1 -
src/include/nodes/parsenodes.h | 20 +------
src/tools/pgindent/typedefs.list | 2 -
8 files changed, 39 insertions(+), 154 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 499bd8e5c0..0b375e38fb 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,12 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -178,6 +178,13 @@ ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sal
</programlisting>
</para>
+ <para>
+ ADD some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
<para>
Drop some schema from the publication:
<programlisting>
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index dd3fb27f28..d6798ac264 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -141,49 +141,6 @@ parse_publication_options(ParseState *pstate,
}
}
-/*
- * Convert the SchemaSpec list into an Oid list.
- */
-static List *
-ConvertSchemaSpecListToOidList(List *schemas)
-{
- List *schemaoidlist = NIL;
- ListCell *cell;
-
- foreach(cell, schemas)
- {
- SchemaSpec *schema = (SchemaSpec *) lfirst(cell);
- Oid schemaoid;
- List *search_path;
- char *nspname;
-
- if (schema->schematype == SCHEMASPEC_CURRENT_SCHEMA)
- {
- search_path = fetch_search_path(false);
- if (search_path == NIL) /* nothing valid in search_path? */
- ereport(ERROR,
- errcode(ERRCODE_UNDEFINED_SCHEMA),
- errmsg("no schema has been selected"));
-
- schemaoid = linitial_oid(search_path);
- nspname = get_namespace_name(schemaoid);
- if (nspname == NULL) /* recently-deleted namespace? */
- ereport(ERROR,
- errcode(ERRCODE_UNDEFINED_SCHEMA),
- errmsg("no schema has been selected"));
-
- list_free(search_path);
- }
- else
- schemaoid = get_namespace_oid(schema->schemaname, false);
-
- /* Filter out duplicates if user specifies "sch1, sch1" */
- schemaoidlist = list_append_unique_oid(schemaoidlist, schemaoid);
- }
-
- return schemaoidlist;
-}
-
/*
* Convert the PublicationObjSpecType list into schema oid list and rangevar
* list.
@@ -531,7 +488,7 @@ InvalidatePublicationRels(List *relids)
*/
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+ HeapTuple tup, List *tables)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
@@ -545,9 +502,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(tables) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(tables);
if (stmt->action == DEFELEM_ADD)
PublicationAddTables(pubid, rels, false, stmt);
@@ -608,9 +565,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
* Add/Remove/Set all tables from schemas to/from publication.
*/
static void
-AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, List *schemaoidlist)
{
- List *schemaoidlist = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Check that user is allowed to manipulate the publication tables */
@@ -627,9 +584,6 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to add or set schemas")));
- /* Convert the text list into oid list */
- schemaoidlist = ConvertSchemaSpecListToOidList(stmt->schemas);
-
/*
* Schema lock is held until the publication is altered to prevent
* concurrent schema deletion. No need to unlock the schemas, the locks
@@ -673,6 +627,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
Relation rel;
HeapTuple tup;
Form_pg_publication pubform;
+ List *relations = NIL;
+ List *schemaoidlist = NIL;
rel = table_open(PublicationRelationId, RowExclusiveLock);
@@ -692,12 +648,18 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
stmt->pubname);
+ ConvertPubObjSpecListToOidList(stmt->pubobjects, pstate, &relations,
+ &schemaoidlist);
+
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
- else if (stmt->schemas)
- AlterPublicationSchemas(stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ if (schemaoidlist)
+ AlterPublicationSchemas(stmt, rel, tup, schemaoidlist);
+ if (relations)
+ AlterPublicationTables(stmt, rel, tup, relations);
+ }
/* Cleanup. */
heap_freetuple(tup);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bcb937c7be..4f15214d8c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4821,8 +4821,7 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
- COPY_NODE_FIELD(schemas);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
COPY_SCALAR_FIELD(action);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 05ca195af8..14af6b9937 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2307,8 +2307,7 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
- COMPARE_NODE_FIELD(schemas);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
COMPARE_SCALAR_FIELD(action);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e408acfce3..53b5d012d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -169,7 +169,6 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
-static SchemaSpec *makeSchemaSpec(SchemaSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -258,7 +257,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
- SchemaSpec *schemaspec;
PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
@@ -429,7 +427,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list schema_list pub_obj_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -556,7 +554,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
-%type <schemaspec> SchemaSpec
%type <publicationobjectspec> PublicationObjSpec
%type <publicationobjectspec> pubobj_expr
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -9705,26 +9702,6 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-/* Schema specifications */
-SchemaSpec: ColId
- {
- SchemaSpec *n;
- n = makeSchemaSpec(SCHEMASPEC_CSTRING, @1);
- n->schemaname = pstrdup($1);
- $$ = n;
- }
- | CURRENT_SCHEMA
- {
- $$ = makeSchemaSpec(SCHEMASPEC_CURRENT_SCHEMA, @1);
- }
- ;
-
-schema_list: SchemaSpec
- { $$ = list_make1($1); }
- | schema_list ',' SchemaSpec
- { $$ = lappend($1, $3); }
- ;
-
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
@@ -9750,51 +9727,27 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE relation_expr_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
+ n->pubobjects = $5;
n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE relation_expr_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
+ n->pubobjects = $5;
n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE relation_expr_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->action = DEFELEM_DROP;
- $$ = (Node *)n;
- }
- | ALTER PUBLICATION name ADD_P ALL TABLES IN_P SCHEMA schema_list
- {
- AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
- n->pubname = $3;
- n->schemas = $9;
- n->action = DEFELEM_ADD;
- $$ = (Node *)n;
- }
- | ALTER PUBLICATION name SET ALL TABLES IN_P SCHEMA schema_list
- {
- AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
- n->pubname = $3;
- n->schemas = $9;
- n->action = DEFELEM_SET;
- $$ = (Node *)n;
- }
- | ALTER PUBLICATION name DROP ALL TABLES IN_P SCHEMA schema_list
- {
- AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
- n->pubname = $3;
- n->schemas = $9;
+ n->pubobjects = $5;
n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
@@ -16744,20 +16697,6 @@ makeRoleSpec(RoleSpecType type, int location)
return spec;
}
-/*
- * makeSchemaSpec - Create a SchemaSpec with the given type and location
- */
-static SchemaSpec *
-makeSchemaSpec(SchemaSpecType type, int location)
-{
- SchemaSpec *spec = makeNode(SchemaSpec);
-
- spec->schematype = type;
- spec->location = location;
-
- return spec;
-}
-
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index af82a2fd7f..bec18e978a 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -484,7 +484,6 @@ typedef enum NodeTag
T_CommonTableExpr,
T_PublicationObjSpec,
T_RoleSpec,
- T_SchemaSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 70146d1fa5..458b25f955 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,23 +341,6 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
-/*
- * SchemaSpec - a schema name or CURRENT_SCHEMA
- */
-typedef enum SchemaSpecType
-{
- SCHEMASPEC_CSTRING, /* schema name is stored as a C string */
- SCHEMASPEC_CURRENT_SCHEMA /* schema spec is CURRENT_SCHEMA */
-} SchemaSpecType;
-
-typedef struct SchemaSpec
-{
- NodeTag type;
- SchemaSpecType schematype; /* type of this schemaspec */
- char *schemaname; /* filled only for SCHEMASPEC_CSTRING */
- int location; /* token location, or -1 if unknown */
-} SchemaSpec;
-
/*
* Publication object type
*/
@@ -3683,8 +3666,7 @@ typedef struct AlterPublicationStmt
List *options; /* List of DefElem nodes */
/* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
- List *tables; /* List of tables to add/drop */
- List *schemas; /* List of schemas to add/drop/set */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
DefElemAction action; /* What action to perform with the
* tables/schemas */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index fc0deec206..2d24e72329 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2336,8 +2336,6 @@ ScanState
ScanTypeControl
ScannerCallbackState
SchemaQuery
-SchemaSpec
-SchemaSpecType
SecBuffer
SecBufferDesc
SecLabelItem
--
2.30.2
v24-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v24-0005-Implemented-pg_publication_objects-view.patchDownload
From 36f9a5c40c6159f500ed87a83ba8176fd8b84472 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v24 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2186a27e27..91a91dedbf 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..81c3d60ccf 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_schema S ON P.oid = S.pspubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.psnspcid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.psnspcid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..a0683cb85f 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_schema s ON ((p.oid = s.pspubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.psnspcid)))
+ JOIN pg_namespace n ON ((n.oid = s.psnspcid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Sat, Aug 28, 2021 at 3:19 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
Don't you think some users might want to know all the schema names for
a publication? I am not completely sure on this point but I think it
is good to have information for users. It might be also useful to have
pg_publication_objects where we can display object types (like table,
schema, sequence, etc) and then object names. If you are not convinced
then we can wait and see what others think about this.
Thanks for the comment, this is handled in the v24 patch attached at [1]/messages/by-id/CALDaNm27bs40Rxpy4oKfV97UgsPG=vVoZ5bj9pP_4BxnO-6DYA@mail.gmail.com.
[1]: /messages/by-id/CALDaNm27bs40Rxpy4oKfV97UgsPG=vVoZ5bj9pP_4BxnO-6DYA@mail.gmail.com
Regards,
Vignesh
On Wed, Sep 1, 2021 at 6:58 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Here are some other comments for v23-000x patches.
1)
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
...
PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
printTableContent cont;Should we delete the inner declaration of 'title' and 'cont' ?
Modified
2) - /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */ + /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE/SCHEMA */SCHEMA => ALL TABLES IN SCHEMA
Modified
3)
+ .description = "PUBLICATION SCHEMA", + .section = SECTION_POST_DATA, + .createStmt = query->data));Is it better to use something like 'PUBLICATION TABLES IN SCHEMA' to describe
the schema level table publication ? Because there could be some other type
publication such as 'ALL SEQUENCES IN SCHEMA' in the future, it will be better
to make it clear that we only publish table in schema in this patch.
Modified
Thanks for the comments, the v24 patch attached at [1]/messages/by-id/CALDaNm27bs40Rxpy4oKfV97UgsPG=vVoZ5bj9pP_4BxnO-6DYA@mail.gmail.com handles the comments.
[1]: /messages/by-id/CALDaNm27bs40Rxpy4oKfV97UgsPG=vVoZ5bj9pP_4BxnO-6DYA@mail.gmail.com
Regards,
Vignesh
On Wed, Sep 1, 2021 at 11:14 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Monday, August 30, 2021 11:28 PM vignesh C <vignesh21@gmail.com> wrote:
I have fixed these comments as part of v23 patch attached at [1].
[1] - https://www.postgresql.org/message-
id/CALDaNm0xmqJeQEfV5Wnj2BawM%3DsdFdfOXz5N%2BgGG3WB6k9%3Dtdw
%40mail.gmail.comThanks for your new patch. Here are some comments on v23 patch.
1. doc/src/sgml/ref/alter_publication.sgml + <para> + Add some schemas to the publication: +<programlisting> +ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june; +</programlisting> + </para>This change seems to be added twice, both 0003 and 0004 patch have this change.
Modified
2. src/sgml/ref/create_publication.sgml
There is the following description about "FOR TABLE" parameter:
Only persistent base tables and partitioned tables can be part of a
publication. Temporary tables, unlogged tables, foreign tables,
materialized views, and regular views cannot be part of a publication."FOR ALL TABLES IN SCHEMA" parameter also have restrictions, should we add
some doc description for it?
Modified
3. When using '\dn+', I noticed that the list of publications only contains the
publications for "SCHEMA", "FOR ALL TABLES" publications are not shown. Is it designed on purpose?
(The result of '\d+' lists the publications of "SCHEAME" and "FOR ALL TABLES").For example:
create schema sch1;
create table sch1.tbl(a int);
create publication pub_schema for all tables in schema sch1;
create publication pub_all_tables for all tables;
I'm not sure if it is intentional or not, Do you want to post the
question in a separate thread and see if that should be handled?
Thanks for the comments, the v24 patch attached at [1]/messages/by-id/CALDaNm27bs40Rxpy4oKfV97UgsPG=vVoZ5bj9pP_4BxnO-6DYA@mail.gmail.com handles the comments.
[1]: /messages/by-id/CALDaNm27bs40Rxpy4oKfV97UgsPG=vVoZ5bj9pP_4BxnO-6DYA@mail.gmail.com
Regards,
Vignesh
From Wed, Sep 1, 2021 2:36 PM Peter Smith <smithpb2250@gmail.com> wrote:
Schema objects are not part of the publication. Current only TABLES are in
publications, so I thought that \dRp+ output would just be the of "Tables" in
the publication. Schemas would not even be displayed at all (except in the
table name).
I think one use case of schema level publication is it can automatically
publish new table created in the shcema(same as ALL TABLE publication). So,
IMO, \dRp+ should output Schema level publication separately to make the user
aware of it.
Best regards,
Hou zj
On Thu, Sep 2, 2021 at 11:58 AM vignesh C <vignesh21@gmail.com> wrote:
Below are few comments on v23. If you have already addressed anything
in v24, then ignore it.
1. The commit message says: A new system table "pg_publication_schema"
has been added, to maintain the schemas that the user wants to publish
through the publication.". The alternative name for this system table
could be "pg_publication_namespace". The reason why this alternative
comes to my mind is that the current system table to store schema
information is named pg_namespace. So shouldn't we be consistent here?
What do others think about this?
2. In function check_publication_add_schema(), the primary error
message should be "cannot add schema \"%s\" to publication". See
check_publication_add_relation() for similar error messages.
3.
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)
Isn't it better to use 'schemaid' so that it is consistent with 'pubid'?
4.
ConvertPubObjSpecListToOidList()
{
..
+ schemaoid = linitial_oid(search_path);
+ nspname = get_namespace_name(schemaoid);
+ if (nspname == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ list_free(search_path);
..
}
You can free the memory for 'nspname' as that is not used afterward.
5.
+ schemaRels = GetSchemaPublicationRelations(schemaoid, PUBLICATION_PART_ALL);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ InvalidatePublicationRels(schemaRels);
It is better to write this comment above
GetSchemaPublicationRelations, something like below:
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaoid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
6.
+static List *
+GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
I think it is better to name this function as
GetPublicationPartOptRelations as that way it will be more consistent
with existing functions and structure name PublicationPartOpt.
7. All the callers of PublicationAddSchemas() have a superuser check,
then why there is again a check of owner/superuser in that function?
8.
+/*
+ * Gets the list of FOR ALL TABLES IN SCHEMA publication oids associated with a
+ * specified schema oid
+ */
+List *
+GetSchemaPublications(Oid schemaid)
I find it a bit difficult to read this comment. Can we omit "FOR ALL
TABLES IN SCHEMA" from this comment?
9. In the doc patch
(v23-0003-Tests-and-documentation-for-schema-level-support), I see
below line:
<para>
- To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ To add a table/schema to a publication, the invoking user must have
+ ownership rights on the table/schema. The <command>FOR ALL TABLES</command>
+ and <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
Is it correct to specify the schema in the first line? AFAIU, all
forms of schema addition requires superuser privilege.
--
With Regards,
Amit Kapila.
On Thu, Sep 2, 2021 at 6:50 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
From Wed, Sep 1, 2021 2:36 PM Peter Smith <smithpb2250@gmail.com> wrote:
Schema objects are not part of the publication. Current only TABLES are in
publications, so I thought that \dRp+ output would just be the of "Tables" in
the publication. Schemas would not even be displayed at all (except in the
table name).I think one use case of schema level publication is it can automatically
publish new table created in the shcema(same as ALL TABLE publication). So,
IMO, \dRp+ should output Schema level publication separately to make the user
aware of it.
OK. That is a fair point.
------
Kind Regards,
Peter Smith.
Fujitsu Australia
On Mon, Aug 30, 2021 at 8:56 PM vignesh C <vignesh21@gmail.com> wrote:
On Mon, Aug 30, 2021 at 9:10 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:5) + if (list_length(pubobj->name) == 1 && + (strcmp(relname, "CURRENT_SCHEMA") == 0)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid relation name at or near"), + parser_errposition(pstate, pubobj->location));Maybe we don't need this check, because it will report an error in
OpenTableList() anyway, "relation "CURRENT_SCHEMA" does not exist" , and that
message seems readable to me.Allowing CURRENT_SCHEMA is required to support current schema for
schema publications, currently I'm allowing this syntax during parsing
and this error is thrown for relations later, this is done to keep the
similar error as earlier before this feature support. I felt we can
keep it like this to maintain the similar error. Thoughts?
I find this check quite ad-hoc in the code and I am not sure if we
need to be consistent for the exact message in this case. So, I think
it is better to remove it.
About 0003
7)
The v22-0003 seems simple and can remove lots of code in patch v22-0001, so
maybe we can merge 0001 and 0003 into one patch ?I agree that the code becomes simpler, it reduces a lot of code. I had
kept it like that as the testing effort might be more and also I was
waiting if there was no objection for that syntax from anyone else. I
will wait for a few more reviews and merge it to 0001 if there are no
objections.
+1 to merge the patch as suggested by Hou-San.
--
With Regards,
Amit Kapila.
On Wed, Sep 1, 2021 at 12:05 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 1, 2021 at 8:52 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
I'd expect a lot of users to naturally think that "ALTER PUBLICATION
pub1 DROP ALL TABLES IN SCHEMA sc1;" would drop from the publication
all tables that are in schema "sc1", which is not what it is currently
doing.
Since the syntax was changed to specifically refer to FOR ALL TABLES
IN SCHEMA rather than FOR SCHEMA, then now it's clear we're referring
to tables only, when specifying "... FOR ALL TABLES in sc1, TABLE
sc1.test", so IMHO it's reasonable to remove duplicates here, rather
than treating these as somehow separate ways of referencing the same
table.I see your point and if we decide to go this path then it is better to
give an error than silently removing duplicates.
Today, I have thought about this point again and it seems better to
give an error in this case and let the user take the action rather
than silently removing such tables to avoid any confusion.
--
With Regards,
Amit Kapila.
On Thu, Sep 2, 2021 at 5:12 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Sep 2, 2021 at 11:58 AM vignesh C <vignesh21@gmail.com> wrote:
Below are few comments on v23. If you have already addressed anything
in v24, then ignore it.
More Review comments (on latest version v24):
=======================================
1.
+ Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */
+} FormData_pg_publication_schema;
Why in the above structure you have used pg_class instead of pg_namespace?
2. GetSchemaPublicationRelations() uses two different ways to skip
non-publishable relations in two while loops. Both are correct but I
think it would be easier to follow if they use the same way and in
this case I would prefer a check like if (is_publishable_class(relid,
relForm)). The comments atop function could also be modified to :"Get
the list of publishable relation oids for a specified schema.". I
think it is better to write some comments on why you need to scan and
loop twice.
3. The other point about GetSchemaPublicationRelations() is that I am
afraid that it will collect duplicate relation oids in the final list
when the partitioned table and child tables are in the same schema.
4. In GetRelationPublicationActions(), the handling related to
partitions seems to be missing for the schema. It won't be able to
take care of child tables when they are in a different schema than the
parent table.
5.
If I modify the search path to remove public schema then I get the
below error message:
postgres=# Create publication mypub for all tables in schema current_schema;
ERROR: no schema has been selected
I think this message is not very clear. How about changing to
something like "current_schema doesn't contain any valid schema"? This
message is used in more than one place, so let's keep it the same at
all the places if you agree to change it.
6. How about naming ConvertPubObjSpecListToOidList() as
ObjectsInPublicationToOids()? I see somewhat similarly named functions
in the code like objectsInSchemaToOids, objectNamesToOids.
7.
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of create
+ * publication command.
+ */
In this comment no need to say create publication command as that is
implicit, we can just write ".... at the end of command".
8. Can you please extract changes like tab-completion, dump/restore in
separate patches? This can help to review the core (backend) part of
the patch in more detail.
--
With Regards,
Amit Kapila.
From Thur, Sep 2, 2021 2:33 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Sep 1, 2021 at 6:58 AM houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com> wrote:
Here are some other comments for v23-000x patches.
3)+ .description =
"PUBLICATION SCHEMA",
+ .section =
SECTION_POST_DATA,
+ .createStmt + = query->data));Is it better to use something like 'PUBLICATION TABLES IN SCHEMA' to
describe the schema level table publication ? Because there could be
some other type publication such as 'ALL SEQUENCES IN SCHEMA' in the
future, it will be better to make it clear that we only publish table in schema inthis patch.
Modified
Thanks for updating the patch.
I think we might also need to mention the publication object 'table' in the
following types:
1)
+ /* OCLASS_PUBLICATION_SCHEMA */
+ {
+ "publication schema", OBJECT_PUBLICATION_SCHEMA
+ },
2)
+ PUBLICATIONOBJ_SCHEMA, /* Schema type */
+ PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+} PublicationObjSpecType;
3)
+ DO_PUBLICATION_SCHEMA,
I think it might be to change the typename like XX_REL_IN_SCHEMA,
and adjust the comments.
Best regards,
Hou zj
From Thur, Sep 2, 2021 7:42 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Sep 2, 2021 at 11:58 AM vignesh C <vignesh21@gmail.com> wrote:
Below are few comments on v23. If you have already addressed anything in v24,
then ignore it.1. The commit message says: A new system table "pg_publication_schema"
has been added, to maintain the schemas that the user wants to publish
through the publication.". The alternative name for this system table could be
"pg_publication_namespace". The reason why this alternative comes to my
mind is that the current system table to store schema information is named
pg_namespace. So shouldn't we be consistent here?
What do others think about this?
Since the new system table refer to pg_namespace, so, personally, I am +1 for
"pg_publication_namespace".
Best regards,
Hou zj
On Thursday, September 2, 2021 2:36 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Sep 1, 2021 at 11:14 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:3. When using '\dn+', I noticed that the list of publications only contains the
publications for "SCHEMA", "FOR ALL TABLES" publications are not shown. Is itdesigned on purpose?
(The result of '\d+' lists the publications of "SCHEAME" and "FOR ALL TABLES").
For example:
create schema sch1;
create table sch1.tbl(a int);
create publication pub_schema for all tables in schema sch1;
create publication pub_all_tables for all tables;I'm not sure if it is intentional or not, Do you want to post the
question in a separate thread and see if that should be handled?
Sorry, maybe I didn't make my last question clearly enough.
In HEAD(where schema level is not supported for publication), there is no publication
information in the result of '\dn+'.
With this schema patch, '\dn+' shows the publications related to the schema, but ALL
TABLES publications are not shown. Do you think we should add ALL TABLES publications, too?
Regards
Tang
On Mon, Sep 6, 2021 at 9:53 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Thursday, September 2, 2021 2:36 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Sep 1, 2021 at 11:14 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:3. When using '\dn+', I noticed that the list of publications only contains the
publications for "SCHEMA", "FOR ALL TABLES" publications are not shown. Is itdesigned on purpose?
(The result of '\d+' lists the publications of "SCHEAME" and "FOR ALL TABLES").
For example:
create schema sch1;
create table sch1.tbl(a int);
create publication pub_schema for all tables in schema sch1;
create publication pub_all_tables for all tables;I'm not sure if it is intentional or not, Do you want to post the
question in a separate thread and see if that should be handled?Sorry, maybe I didn't make my last question clearly enough.
In HEAD(where schema level is not supported for publication), there is no publication
information in the result of '\dn+'.With this schema patch, '\dn+' shows the publications related to the schema, but ALL
TABLES publications are not shown. Do you think we should add ALL TABLES publications, too?
No, I don't think we need to display For All Tables publication under
\dn+. It is already shown with \d+ <table_name> command.
--
With Regards,
Amit Kapila.
On Thu, Sep 2, 2021 at 4:28 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v24 patch has the changes for the same.
Just a note that these patches no longer apply due to a recent commit
related to logical replication (0c6828fa98).
Regards,
Greg Nancarrow
Fujitsu Australia
On Thu, Sep 2, 2021 at 5:12 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Sep 2, 2021 at 11:58 AM vignesh C <vignesh21@gmail.com> wrote:
Below are few comments on v23. If you have already addressed anything
in v24, then ignore it.1. The commit message says: A new system table "pg_publication_schema"
has been added, to maintain the schemas that the user wants to publish
through the publication.". The alternative name for this system table
could be "pg_publication_namespace". The reason why this alternative
comes to my mind is that the current system table to store schema
information is named pg_namespace. So shouldn't we be consistent here?
What do others think about this?
Modified
2. In function check_publication_add_schema(), the primary error
message should be "cannot add schema \"%s\" to publication". See
check_publication_add_relation() for similar error messages.
Modified
3. +ObjectAddress +publication_add_schema(Oid pubid, Oid schemaoid, bool if_not_exists)Isn't it better to use 'schemaid' so that it is consistent with 'pubid'?
Modified
4. ConvertPubObjSpecListToOidList() { .. + schemaoid = linitial_oid(search_path); + nspname = get_namespace_name(schemaoid); + if (nspname == NULL) /* recently-deleted namespace? */ + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("no schema has been selected")); + + list_free(search_path); .. }You can free the memory for 'nspname' as that is not used afterward.
I have removed get_namespace_name, no need to validate the schemaid,
fetch_search_path will return valid schemaids.
5. + schemaRels = GetSchemaPublicationRelations(schemaoid, PUBLICATION_PART_ALL); + + /* Invalidate relcache so that publication info is rebuilt. */ + InvalidatePublicationRels(schemaRels);It is better to write this comment above
GetSchemaPublicationRelations, something like below:+ /* Invalidate relcache so that publication info is rebuilt. */ + schemaRels = GetSchemaPublicationRelations(schemaoid, PUBLICATION_PART_ALL); + InvalidatePublicationRels(schemaRels);
Modified
6. +static List * +GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt, + Oid relid)I think it is better to name this function as
GetPublicationPartOptRelations as that way it will be more consistent
with existing functions and structure name PublicationPartOpt.
Modified
7. All the callers of PublicationAddSchemas() have a superuser check,
then why there is again a check of owner/superuser in that function?
Modified to remove the check
8. +/* + * Gets the list of FOR ALL TABLES IN SCHEMA publication oids associated with a + * specified schema oid + */ +List * +GetSchemaPublications(Oid schemaid)I find it a bit difficult to read this comment. Can we omit "FOR ALL
TABLES IN SCHEMA" from this comment?
Modified.
9. In the doc patch (v23-0003-Tests-and-documentation-for-schema-level-support), I see below line: <para> - To add a table to a publication, the invoking user must have ownership - rights on the table. The <command>FOR ALL TABLES</command> clause requires - the invoking user to be a superuser. + To add a table/schema to a publication, the invoking user must have + ownership rights on the table/schema. The <command>FOR ALL TABLES</command> + and <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking + user to be a superuser.Is it correct to specify the schema in the first line? AFAIU, all
forms of schema addition requires superuser privilege.
That is not required, modified.
Attached v25 patch has the changes for the same.
Currently I have used a different way to parse which does not require
"Add PublicationTable and PublicationRelInfo structs" committed
changes, I have currently removed the changes in this patch. I will
analyze further and use whichever parsing is better. I will handle
this in the next version.
Regards,
Vignesh
Attachments:
v25-0001-Made-the-existing-relation-cache-invalidation-an.patchtext/x-patch; charset=US-ASCII; name=v25-0001-Made-the-existing-relation-cache-invalidation-an.patchDownload
From 5ff52bd1786def71eb47c69abf20b4b863a2b227 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 17:20:53 +0530
Subject: [PATCH v25 1/6] Made the existing relation cache invalidation and
getting the relations based on the publication partition option for a
specified relation into a function.
Made the existing relation cache invalidation code into a function. Also
made getting the relations based on the publication partition option for a
specified relation into a function. This will be used in the later
"FOR ALL TABLES IN SCHEMA" implementation patch.
---
src/backend/catalog/pg_publication.c | 67 +++++++++++++++-----------
src/backend/commands/publicationcmds.c | 42 ++++++++--------
src/include/commands/publicationcmds.h | 5 ++
3 files changed, 67 insertions(+), 47 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index d6fddd6efe..10dfe96bb2 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -238,10 +238,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,32 +307,8 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
-
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
-
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
-
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
-
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ pubrel->prrelid);
}
systable_endscan(scan);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 179a0ef982..9ce412a313 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -45,9 +45,6 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
@@ -324,23 +321,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
List *relids = GetPublicationRelations(pubform->oid,
PUBLICATION_PART_ALL);
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
-
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
@@ -350,6 +331,27 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
InvokeObjectPostAlterHook(PublicationRelationId, pubform->oid, 0);
}
+/*
+ * Invalidate the relations.
+ */
+void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point it's
+ * cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
/*
* Add or remove table to/from publication.
*/
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index a3fa2ac6cd..fa47d6d761 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -17,6 +17,10 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
@@ -24,5 +28,6 @@ extern void RemovePublicationRelById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern void InvalidatePublicationRels(List *relids);
#endif /* PUBLICATIONCMDS_H */
--
2.30.2
v25-0002-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v25-0002-Added-schema-level-support-for-publication.patchDownload
From c0883f92730cef0d4a8708ba2c9b2857d20b5826 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Tue, 7 Sep 2021 11:53:24 +0530
Subject: [PATCH v25 2/6] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 150 ++++++
src/backend/catalog/pg_publication.c | 298 +++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 443 ++++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 19 +-
src/backend/nodes/equalfuncs.c | 17 +-
src/backend/parser/gram.y | 152 ++++--
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 10 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 38 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 6 +-
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
27 files changed, 1111 insertions(+), 155 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 76b65e39c4..dcd444c9cb 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2847,6 +2853,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index deaabaeae9..db1feb8f8d 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,50 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication tables in schema. The first
+ * element of the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2261,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2355,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2905,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspcid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnpubid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3903,6 +4009,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4473,6 +4595,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5712,6 +5838,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 10dfe96bb2..b579bab6d2 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -75,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -141,14 +167,14 @@ pg_relation_is_publishable(PG_FUNCTION_ARGS)
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -172,10 +198,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -209,7 +235,85 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
table_close(rel, RowExclusiveLock);
/* Invalidate relcache so that publication info is rebuilt. */
- CacheInvalidateRelcache(targetrel->relation);
+ CacheInvalidateRelcache(targetrel);
+
+ return myself;
+}
+
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspcid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
return myself;
}
@@ -253,7 +357,7 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -263,14 +367,14 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -317,6 +421,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspcidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspcid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -355,7 +526,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -417,6 +588,99 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(schemaid != InvalidOid);
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the ordinary tables present in schema schemaid */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * Get all relations that inherit from the partition table, directly or
+ * indirectly.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -546,10 +810,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..cef9deb589 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9ce412a313..0d97e0799f 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,12 +36,12 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -50,6 +52,10 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +141,133 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_UNKNOWN;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ RangeVar *rel;
+
+ rel = makeRangeVarFromNameList(pubobj->name);
+ rel->inh = pubobj->inh;
+ rel->location = pubobj->location;
+ *rels = lappend(*rels, rel);
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_SCHEMA)
+ {
+ Oid schemaid;
+ char *schemaname;
+
+ if (list_length(pubobj->name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(pubobj->name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ if (pubobj->spl_rel_type_syn)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(linitial(pubobj->name));
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+ }
+ else
+ schemaid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
+ }
+}
+
+/*
+ * Check if the relation specified is part of the schema list.
+ */
+static void
+CheckRelschemaInSchemaList(List *rels, List *schemaidlist)
+{
+ ListCell *lc;
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema is already included as part of ALL TABLES IN SCHEMA option."));
+ }
+}
+
+/*
+ * Check if the schemaid specified is part of the schema list.
+ */
+static void
+CheckSchemaInRels(List *schemaidlist, List *rels)
+{
+ ListCell *lc;
+ foreach(lc, rels)
+ {
+ Oid tableOid = (Oid) lfirst_oid(lc);
+ Oid relSchemaId = get_rel_namespace(tableOid);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(get_rel_namespace(tableOid))),
+ errdetail("Table \"%s.%s\" is part of publication, adding same schema \"%s\" is not supported",
+ get_namespace_name(get_rel_namespace(tableOid)),
+ get_rel_name(tableOid),
+ get_namespace_name(get_rel_namespace(tableOid))));
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +285,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,19 +356,40 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
+ CheckRelschemaInSchemaList(rels, schemaidlist);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- table_close(rel, RowExclusiveLock);
+ if (schemaidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+ Assert(list_length(schemaidlist) > 0);
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of the command.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+
+ table_close(rel, RowExclusiveLock);
InvokeObjectPostCreateHook(PublicationRelationId, puboid, 0);
if (wal_level != WAL_LEVEL_LOGICAL)
@@ -313,13 +469,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -357,7 +519,7 @@ InvalidatePublicationRels(List *relids)
*/
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+ HeapTuple tup, List *tables)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
@@ -369,15 +531,20 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ errdetail("Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ Assert(list_length(tables) > 0);
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *pubschemas = GetPublicationSchemas(pubid);
- if (stmt->tableAction == DEFELEM_ADD)
+ CheckRelschemaInSchemaList(rels, pubschemas);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -385,6 +552,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PUBLICATION_PART_ROOT);
List *delrels = NIL;
ListCell *oldlc;
+ List *pubschemas = GetPublicationSchemas(pubid);
+
+ CheckRelschemaInSchemaList(rels, pubschemas);
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
@@ -395,10 +565,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -407,16 +576,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -435,11 +598,77 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ CheckSchemaInRels(schemaidlist, rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ List *rels;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ CheckSchemaInRels(schemaidlist, rels);
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -447,6 +676,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
Relation rel;
HeapTuple tup;
Form_pg_publication pubform;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
rel = table_open(PublicationRelationId, RowExclusiveLock);
@@ -466,10 +697,18 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
stmt->pubname);
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ if (schemaidlist)
+ AlterPublicationSchemas(stmt, rel, tup, schemaidlist);
+ if (relations)
+ AlterPublicationTables(stmt, rel, tup, relations);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -507,9 +746,61 @@ RemovePublicationRelById(Oid proid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspcid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -523,16 +814,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -548,9 +838,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -583,9 +871,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -606,10 +892,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -626,8 +911,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -635,7 +919,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -647,6 +931,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -659,8 +971,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -682,6 +993,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..f91b9963c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e308de170e..11afed7c26 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4808,7 +4808,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4821,9 +4821,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4939,16 +4939,6 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
-{
- PublicationTable *newnode = makeNode(PublicationTable);
-
- COPY_NODE_FIELD(relation);
-
- return newnode;
-}
-
/*
* copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
*
@@ -5863,9 +5853,6 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
- break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 99440b40be..14af6b9937 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2295,7 +2295,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2307,9 +2307,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3114,14 +3114,6 @@ _equalValue(const Value *a, const Value *b)
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
/*
* equal
* returns whether two nodes are equal
@@ -3870,9 +3862,6 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
- break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6a0f46505c..ca8b16b4b4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -257,6 +257,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +427,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -554,7 +554,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
-
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,58 +9592,114 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
*
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES IN SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+pubobj_expr:
+ any_name
+ {
+ /* inheritance query, implicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = false;
+ $$ = n;
+ }
+ | any_name '*'
+ {
+ /* inheritance query, explicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY any_name
{
- $$ = (Node *) $3;
+ /* no inheritance */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $2;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
}
- | FOR ALL TABLES
+ | ONLY '(' any_name ')'
{
- $$ = (Node *) makeInteger(true);
+ /* no inheritance, SQL99-style syntax */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $3;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = list_make1(makeString("CURRENT_SCHEMA"));
+ n->inh = false;
+ n->spl_rel_type_syn = false;
+ $$ = n;
}
;
-publication_table_list:
- publication_table
- { $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_UNKNOWN;
+ $$->location = @1;
+ }
;
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
@@ -9655,6 +9712,11 @@ publication_table: relation_expr
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET ALL TABLES IN SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9665,28 +9727,28 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..7c5906609b 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspcidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspcid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 561266aa3e..9ceb2d5e0f 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -111,10 +111,18 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..5dc0667785
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspcid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspcid_pnpubid_index, 8903, PublicationNamespacePnnspcidPnpubidIndexId, on pg_publication_namespace using btree(pnnspcid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index fa47d6d761..276f77fa8c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -25,6 +25,7 @@
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 56d13ff022..bec18e978a 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -482,6 +482,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -490,7 +491,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 743e5aa4f3..e412b62c43 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,28 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_SCHEMA, /* Schema type */
+ PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ List *name; /* publication object name */
+ bool inh; /* expand rel by inheritance? recursively act
+ * on children? */
+ bool spl_rel_type_syn; /* true if it is special relation type
+ * syntax */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1804,6 +1826,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3624,18 +3647,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3647,10 +3664,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..f2b49eabaf 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspcid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..23131de23f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -60,15 +60,15 @@ CREATE TABLE testpub_tbl2 (id serial primary key, data text);
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
-- fail - can't drop from all tables publication
ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
-DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 423780652f..1f2745f2ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2045,9 +2047,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v25-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v25-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From e35a42c6784f5ec726d7bc7ea0c20f4f05be622e Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:43:18 +0530
Subject: [PATCH v25 3/6] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 +++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 192 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 32 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 346 insertions(+), 51 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 67be849829..c458bddf88 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1630,9 +1630,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3960,21 +3964,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4125,6 +4133,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pspubid;
+ int i_psnspcid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspcid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pspubid = PQfnumber(res, "pnpubid");
+ i_psnspcid = PQfnumber(res, "pnnspcid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pspubid));
+ Oid pnnspcid = atooid(PQgetvalue(res, i, i_psnspcid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspcid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4212,6 +4308,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10479,6 +10613,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18734,6 +18871,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..e19e09726e 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..200cc3edb5 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_SCHEMA, /* DO_PUBLICATION_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..0eb1a08556 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspcid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspcid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6407,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6443,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6453,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspcid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6479,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 75b867685a..133a5a73a0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,26 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN
+ * SCHEMA <schema>, ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 1f2745f2ab..a8824880dc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2051,6 +2051,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v25-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v25-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 9e3d1539cb3bcde15cf85523213ef9d1859921a5 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:44:35 +0530
Subject: [PATCH v25 4/6] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 487 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 229 +++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
6 files changed, 918 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index e1b7e31458..00fbb236bf 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2300,6 +2300,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2336,6 +2345,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 23131de23f..e7a13091b5 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,93 @@ DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publi
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema is already included as part of ALL TABLES IN SCHEMA option.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+ERROR: relation "testpub_nopk" is not part of the publication
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema is already included as part of ALL TABLES IN SCHEMA option.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +181,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -246,18 +333,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -289,11 +379,404 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspcid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspcid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspcid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspcid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspcid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d844075368..4f88a9e9d2 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -133,9 +166,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -143,12 +178,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -169,11 +204,201 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspcid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspcid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspcid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspcid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspcid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v25-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v25-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From f4b70894b9130d6d9a7e659be98f392289382e34 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v25 5/6] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 73 ++++++++++++++++++++++--
doc/src/sgml/ref/create_publication.sgml | 69 +++++++++++++++++++---
3 files changed, 200 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..07eb17b013 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pspubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>psnspcid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..b3cef9dafd 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,9 +21,12 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,21 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which all tables
+ in schema are part of the publication. The
+ <literal>SET ALL TABLES IN SCHEMA</literal> clause will replace the list of
+ all tables in schemas of the publication with the specified one. The
+ <literal>ADD ALL TABLES IN SCHEMA</literal> clause will add the list of all
+ tables in schemas to the publication and
+ <literal>DROP ALL TABLES IN SCHEMA</literal> clause will remove the list of
+ all tables in schemas from the publication. Note that adding
+ schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -63,12 +80,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
+ The <literal>ADD SCHEMA</literal> and <literal>SET SCHEMA</literal> to a
+ publication requires the invoking user to be a superuser.
To alter the owner, you must also be a direct or indirect member of the new
owning role. The new owner must have <literal>CREATE</literal> privilege on
the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
publication must be a superuser. However, a superuser can change the
ownership of a publication regardless of these restrictions.
</para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
+ </para>
</refsect1>
<refsect1>
@@ -97,6 +126,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +179,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ ADD some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..aaced75dde 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | [ FOR { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ], }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ] } ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +87,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of schema specified in
+ <literal>FOR ALL TABLES IN SCHEMA</literal> option is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +105,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with schema's table specified as part of
+ <literal>FOR TABLE</literal> option is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +181,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +200,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +252,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v25-0006-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v25-0006-Implemented-pg_publication_objects-view.patchDownload
From a7e32ba3b850af56337d4479176d2aced510f536 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v25 6/6] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 07eb17b013..ab293736df 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..67ce9a306a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspcid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspcid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1a40f8c018 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspcid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspcid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Fri, Sep 3, 2021 at 3:12 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Aug 30, 2021 at 8:56 PM vignesh C <vignesh21@gmail.com> wrote:
On Mon, Aug 30, 2021 at 9:10 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:5) + if (list_length(pubobj->name) == 1 && + (strcmp(relname, "CURRENT_SCHEMA") == 0)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid relation name at or near"), + parser_errposition(pstate, pubobj->location));Maybe we don't need this check, because it will report an error in
OpenTableList() anyway, "relation "CURRENT_SCHEMA" does not exist" , and that
message seems readable to me.Allowing CURRENT_SCHEMA is required to support current schema for
schema publications, currently I'm allowing this syntax during parsing
and this error is thrown for relations later, this is done to keep the
similar error as earlier before this feature support. I felt we can
keep it like this to maintain the similar error. Thoughts?I find this check quite ad-hoc in the code and I am not sure if we
need to be consistent for the exact message in this case. So, I think
it is better to remove it.
Modified
About 0003
7)
The v22-0003 seems simple and can remove lots of code in patch v22-0001, so
maybe we can merge 0001 and 0003 into one patch ?I agree that the code becomes simpler, it reduces a lot of code. I had
kept it like that as the testing effort might be more and also I was
waiting if there was no objection for that syntax from anyone else. I
will wait for a few more reviews and merge it to 0001 if there are no
objections.+1 to merge the patch as suggested by Hou-San.
Modified
This is handled as part of v25 patch attached at [1]/messages/by-id/CALDaNm2SytXy2TDnzzYkXWKgNp74ssPBXrkMXEyac1qVYSRkbw@mail.gmail.com
[1]: /messages/by-id/CALDaNm2SytXy2TDnzzYkXWKgNp74ssPBXrkMXEyac1qVYSRkbw@mail.gmail.com
Regards,
Vignesh
On Mon, Sep 6, 2021 at 6:56 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
From Thur, Sep 2, 2021 7:42 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Sep 2, 2021 at 11:58 AM vignesh C <vignesh21@gmail.com> wrote:
Below are few comments on v23. If you have already addressed anything in v24,
then ignore it.1. The commit message says: A new system table "pg_publication_schema"
has been added, to maintain the schemas that the user wants to publish
through the publication.". The alternative name for this system table could be
"pg_publication_namespace". The reason why this alternative comes to my
mind is that the current system table to store schema information is named
pg_namespace. So shouldn't we be consistent here?
What do others think about this?Since the new system table refer to pg_namespace, so, personally, I am +1 for
"pg_publication_namespace".
I have changed it as part of v25 patch attached at [1]/messages/by-id/CALDaNm2SytXy2TDnzzYkXWKgNp74ssPBXrkMXEyac1qVYSRkbw@mail.gmail.com
[1]: /messages/by-id/CALDaNm2SytXy2TDnzzYkXWKgNp74ssPBXrkMXEyac1qVYSRkbw@mail.gmail.com
Regards,
Vignesh
On Fri, Sep 3, 2021 at 4:49 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Sep 2, 2021 at 5:12 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Sep 2, 2021 at 11:58 AM vignesh C <vignesh21@gmail.com> wrote:
Below are few comments on v23. If you have already addressed anything
in v24, then ignore it.More Review comments (on latest version v24): ======================================= 1. + Oid psnspcid BKI_LOOKUP(pg_class); /* Oid of the schema */ +} FormData_pg_publication_schema;Why in the above structure you have used pg_class instead of pg_namespace?
It should be pg_namespace, I have changed it.
2. GetSchemaPublicationRelations() uses two different ways to skip
non-publishable relations in two while loops. Both are correct but I
think it would be easier to follow if they use the same way and in
this case I would prefer a check like if (is_publishable_class(relid,
relForm)). The comments atop function could also be modified to :"Get
the list of publishable relation oids for a specified schema.". I
think it is better to write some comments on why you need to scan and
loop twice.
Modified
3. The other point about GetSchemaPublicationRelations() is that I am
afraid that it will collect duplicate relation oids in the final list
when the partitioned table and child tables are in the same schema.
Modified it to prepare a list without duplicate relation ids.
4. In GetRelationPublicationActions(), the handling related to
partitions seems to be missing for the schema. It won't be able to
take care of child tables when they are in a different schema than the
parent table.
Modified
5.
If I modify the search path to remove public schema then I get the
below error message:
postgres=# Create publication mypub for all tables in schema current_schema;
ERROR: no schema has been selectedI think this message is not very clear. How about changing to
something like "current_schema doesn't contain any valid schema"? This
message is used in more than one place, so let's keep it the same at
all the places if you agree to change it.
I would prefer to use the existing messages as we have used this in a
few other places similarly. Thoughts?
6. How about naming ConvertPubObjSpecListToOidList() as
ObjectsInPublicationToOids()? I see somewhat similarly named functions
in the code like objectsInSchemaToOids, objectNamesToOids.
Modified
7. + /* + * Schema lock is held until the publication is created to prevent + * concurrent schema deletion. No need to unlock the schemas, the + * locks will be released automatically at the end of create + * publication command. + */In this comment no need to say create publication command as that is
implicit, we can just write ".... at the end of command".
Modified
8. Can you please extract changes like tab-completion, dump/restore in
separate patches? This can help to review the core (backend) part of
the patch in more detail.
Modified
This is handled as part of v25 patch attached at [1]/messages/by-id/CALDaNm2SytXy2TDnzzYkXWKgNp74ssPBXrkMXEyac1qVYSRkbw@mail.gmail.com
[1]: /messages/by-id/CALDaNm2SytXy2TDnzzYkXWKgNp74ssPBXrkMXEyac1qVYSRkbw@mail.gmail.com
Regards,
Vignesh
On Fri, Sep 3, 2021 at 4:06 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 1, 2021 at 12:05 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 1, 2021 at 8:52 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
I'd expect a lot of users to naturally think that "ALTER PUBLICATION
pub1 DROP ALL TABLES IN SCHEMA sc1;" would drop from the publication
all tables that are in schema "sc1", which is not what it is currently
doing.
Since the syntax was changed to specifically refer to FOR ALL TABLES
IN SCHEMA rather than FOR SCHEMA, then now it's clear we're referring
to tables only, when specifying "... FOR ALL TABLES in sc1, TABLE
sc1.test", so IMHO it's reasonable to remove duplicates here, rather
than treating these as somehow separate ways of referencing the same
table.I see your point and if we decide to go this path then it is better to
give an error than silently removing duplicates.Today, I have thought about this point again and it seems better to
give an error in this case and let the user take the action rather
than silently removing such tables to avoid any confusion.
I have handled this to throw an error. This is handled as part of v25
patch attached at [1]/messages/by-id/CALDaNm2SytXy2TDnzzYkXWKgNp74ssPBXrkMXEyac1qVYSRkbw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm2SytXy2TDnzzYkXWKgNp74ssPBXrkMXEyac1qVYSRkbw@mail.gmail.com
Regards,
Vignesh
On Tue, Sep 7, 2021 at 12:45 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Sep 3, 2021 at 4:49 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
5.
If I modify the search path to remove public schema then I get the
below error message:
postgres=# Create publication mypub for all tables in schema current_schema;
ERROR: no schema has been selectedI think this message is not very clear. How about changing to
something like "current_schema doesn't contain any valid schema"? This
message is used in more than one place, so let's keep it the same at
all the places if you agree to change it.I would prefer to use the existing messages as we have used this in a
few other places similarly. Thoughts?
Yeah, I also see the same message in code but I think here usage is a
bit different. If you see a similar SQL statement that causes the same
error message then can you please give an example?
Few comments on latest patch
(v25-0002-Added-schema-level-support-for-publication):
=====================================================================
1.
getPublicationSchemaInfo()
..
+ *nspname = get_namespace_name(pnform->pnnspcid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnpubid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
Why are you using pnform->pnpubid to get schemaid? I think you need
pnform->pnnspcid here and it was like that in the previous version,
not sure, why you changed it?
2.
@@ -369,15 +531,20 @@ AlterPublicationTables(AlterPublicationStmt
*stmt, Relation rel,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("publication \"%s\" is defined as FOR ALL TABLES",
NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES
publications.")));
+ errdetail("Tables cannot be added to, dropped from, or set on FOR
ALL TABLES publications.")));
Why is this message changed? Have we changed anything related to this
as part of this patch?
3.
+ Oid pnnspcid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
...
...
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspcid_pnpubid_index,
8903, PublicationNamespacePnnspcidPnpubidIndexId, on
pg_publication_namespace using btree(pnnspcid oid_ops, pnpubid
oid_ops));
Let's use nspid instead nspcid at all places as before we refer that
way in the code. The way you have done is also okay but it is better
to be consistent with existing code.
4. Need to think of comments in GetSchemaPublicationRelations.
+ /* get all the ordinary tables present in schema schemaid */
..
Let's change the above comment to something like: "get all the
relations present in the given schema"
+
+ /*
+ * Get all relations that inherit from the partition table, directly or
+ * indirectly.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
Let's change the above comment to something like: "It is quite
possible that some of the partitions are in a different schema than
the parent table, so we need to get such partitions separately."
5.
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema is already included as part of ALL TABLES
IN SCHEMA option."));
I think the better errdetail would be: "Table's schema \"%s\" is
already part of the publication."
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(get_rel_namespace(tableOid))),
+ errdetail("Table \"%s.%s\" is part of publication, adding same
schema \"%s\" is not supported",
+ get_namespace_name(get_rel_namespace(tableOid)),
+ get_rel_name(tableOid),
+ get_namespace_name(get_rel_namespace(tableOid))));
I think this errdetail message can also be improved. One idea could
be: "Table \"%s\" in schema \"%s\" is already part of the publication,
adding the same schema is not supported.". Do you or anyone else have
better ideas?
6. I think instead of two different functions
CheckRelschemaInSchemaList and CheckSchemaInRels, let's have a single
function RelSchemaIsMemberOfSchemaList and have a boolean variable to
distinguish the two cases.
--
With Regards,
Amit Kapila.
On Tue, Sep 7, 2021 at 5:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Sep 7, 2021 at 12:45 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Sep 3, 2021 at 4:49 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
5.
If I modify the search path to remove public schema then I get the
below error message:
postgres=# Create publication mypub for all tables in schema current_schema;
ERROR: no schema has been selectedI think this message is not very clear. How about changing to
something like "current_schema doesn't contain any valid schema"? This
message is used in more than one place, so let's keep it the same at
all the places if you agree to change it.I would prefer to use the existing messages as we have used this in a
few other places similarly. Thoughts?Yeah, I also see the same message in code but I think here usage is a
bit different. If you see a similar SQL statement that causes the same
error message then can you please give an example?
I was referring to the error message in create table
postgres=# set search_path='non_existent_schema';
SET
postgres=# create table t1(c1 int);
ERROR: no schema has been selected to create in
LINE 1: create table t1(c1 int);
If it is not very useful in the case of creating a publication, then I
can change it. Thoughts?
Regards,
Vignesh
On Wed, Sep 8, 2021 at 10:48 AM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Sep 7, 2021 at 5:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Sep 7, 2021 at 12:45 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Sep 3, 2021 at 4:49 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
5.
If I modify the search path to remove public schema then I get the
below error message:
postgres=# Create publication mypub for all tables in schema current_schema;
ERROR: no schema has been selectedI think this message is not very clear. How about changing to
something like "current_schema doesn't contain any valid schema"? This
message is used in more than one place, so let's keep it the same at
all the places if you agree to change it.I would prefer to use the existing messages as we have used this in a
few other places similarly. Thoughts?Yeah, I also see the same message in code but I think here usage is a
bit different. If you see a similar SQL statement that causes the same
error message then can you please give an example?I was referring to the error message in create table
postgres=# set search_path='non_existent_schema';
SET
postgres=# create table t1(c1 int);
ERROR: no schema has been selected to create in
LINE 1: create table t1(c1 int);If it is not very useful in the case of creating a publication, then I
can change it. Thoughts?
If you want to be consistent with the existing message then why did
you left the trailing part (".... to create in") of the sentence?
--
With Regards,
Amit Kapila.
On Tue, Sep 7, 2021 at 5:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Sep 7, 2021 at 12:45 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Sep 3, 2021 at 4:49 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
5.
If I modify the search path to remove public schema then I get the
below error message:
postgres=# Create publication mypub for all tables in schema current_schema;
ERROR: no schema has been selectedI think this message is not very clear. How about changing to
something like "current_schema doesn't contain any valid schema"? This
message is used in more than one place, so let's keep it the same at
all the places if you agree to change it.I would prefer to use the existing messages as we have used this in a
few other places similarly. Thoughts?Yeah, I also see the same message in code but I think here usage is a
bit different. If you see a similar SQL statement that causes the same
error message then can you please give an example?
Changed it to "no schema has been selected for CURRENT_SCHEMA"
Few comments on latest patch (v25-0002-Added-schema-level-support-for-publication): ===================================================================== 1. getPublicationSchemaInfo() .. + *nspname = get_namespace_name(pnform->pnnspcid); + if (!(*nspname)) + { + Oid schemaid = pnform->pnpubid; + + pfree(*pubname); + ReleaseSysCache(tup); + if (!missing_ok) + elog(ERROR, "cache lookup failed for schema %u", + schemaid);Why are you using pnform->pnpubid to get schemaid? I think you need
pnform->pnnspcid here and it was like that in the previous version,
not sure, why you changed it?
Modified
2. @@ -369,15 +531,20 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("publication \"%s\" is defined as FOR ALL TABLES", NameStr(pubform->pubname)), - errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications."))); + errdetail("Tables cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));Why is this message changed? Have we changed anything related to this
as part of this patch?
This change is not required in this patch, reverted
3. + Oid pnnspcid BKI_LOOKUP(pg_namespace); /* Oid of the schema */ +} FormData_pg_publication_namespace; + ... ... +DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspcid_pnpubid_index, 8903, PublicationNamespacePnnspcidPnpubidIndexId, on pg_publication_namespace using btree(pnnspcid oid_ops, pnpubid oid_ops));Let's use nspid instead nspcid at all places as before we refer that
way in the code. The way you have done is also okay but it is better
to be consistent with existing code.
Modified
4. Need to think of comments in GetSchemaPublicationRelations.
+ /* get all the ordinary tables present in schema schemaid */
..Let's change the above comment to something like: "get all the
relations present in the given schema"+ + /* + * Get all relations that inherit from the partition table, directly or + * indirectly. + */ + scan = table_beginscan_catalog(classRel, keycount, key);Let's change the above comment to something like: "It is quite
possible that some of the partitions are in a different schema than
the parent table, so we need to get such partitions separately."
Modified
5. + if (list_member_oid(schemaidlist, relSchemaId)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot add relation \"%s.%s\" to publication", + get_namespace_name(relSchemaId), + RelationGetRelationName(rel)), + errdetail("Table's schema is already included as part of ALL TABLES IN SCHEMA option."));I think the better errdetail would be: "Table's schema \"%s\" is
already part of the publication."+ if (list_member_oid(schemaidlist, relSchemaId)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot add schema \"%s\" to publication", + get_namespace_name(get_rel_namespace(tableOid))), + errdetail("Table \"%s.%s\" is part of publication, adding same schema \"%s\" is not supported", + get_namespace_name(get_rel_namespace(tableOid)), + get_rel_name(tableOid), + get_namespace_name(get_rel_namespace(tableOid))));I think this errdetail message can also be improved. One idea could
be: "Table \"%s\" in schema \"%s\" is already part of the publication,
adding the same schema is not supported.". Do you or anyone else have
better ideas?
Modified
6. I think instead of two different functions
CheckRelschemaInSchemaList and CheckSchemaInRels, let's have a single
function RelSchemaIsMemberOfSchemaList and have a boolean variable to
distinguish the two cases.
Modified
Thanks for the comments, the attached v26 patch has the changes for the same.
Regards,
VIgnesh
Attachments:
v26-0002-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v26-0002-Added-schema-level-support-for-publication.patchDownload
From 07e495dd3d07584931441450dde46233e31ed25d Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 17:01:27 +0530
Subject: [PATCH v26 2/6] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 150 ++++++
src/backend/catalog/pg_publication.c | 299 +++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 439 ++++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 19 +-
src/backend/nodes/equalfuncs.c | 17 +-
src/backend/parser/gram.y | 151 ++++--
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 10 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 38 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1106 insertions(+), 150 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..5a0d4a8232 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..2c785e9a5e 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index deaabaeae9..8eb4b3ab41 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_REL_IN_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1125,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1946,50 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication tables in schema. The first
+ * element of the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2262,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2355,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2905,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3903,6 +4009,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4473,6 +4595,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5712,6 +5838,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 10dfe96bb2..c59fef202b 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -75,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -141,14 +167,14 @@ pg_relation_is_publishable(PG_FUNCTION_ARGS)
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -172,10 +198,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -209,7 +235,85 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
table_close(rel, RowExclusiveLock);
/* Invalidate relcache so that publication info is rebuilt. */
- CacheInvalidateRelcache(targetrel->relation);
+ CacheInvalidateRelcache(targetrel);
+
+ return myself;
+}
+
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
return myself;
}
@@ -253,7 +357,7 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -263,14 +367,14 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -317,6 +421,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -355,7 +526,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -417,6 +588,100 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(schemaid != InvalidOid);
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -546,10 +811,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 29249498a9..cef9deb589 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..d3c13ce5ce 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 945df49078..c3f0a0d9bb 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,12 +36,12 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -50,6 +52,10 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +141,129 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_UNKNOWN;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ RangeVar *rel;
+
+ rel = makeRangeVarFromNameList(pubobj->name);
+ rel->inh = pubobj->inh;
+ rel->location = pubobj->location;
+ *rels = lappend(*rels, rel);
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ Oid schemaid;
+ char *schemaname;
+
+ if (list_length(pubobj->name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(pubobj->name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ if (pubobj->spl_rel_type_syn)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(linitial(pubobj->name));
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+ }
+ else
+ schemaid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
+ }
+}
+
+/*
+ * Check if the relation schema is member of the schema list.
+ */
+static void
+RelSchemaIsMemberOfSchemaList(List *rels, List *schemaidlist, bool schemacheck)
+{
+ ListCell *lc;
+ Oid relSchemaId;
+
+ foreach(lc, rels)
+ {
+ if (schemacheck)
+ {
+ Oid tableid = (Oid) lfirst_oid(lc);
+ relSchemaId = get_rel_namespace(tableid);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(get_rel_namespace(tableid))),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ get_rel_name(tableid),
+ get_namespace_name(get_rel_namespace(tableid))));
+ }
+ else
+ {
+ Relation rel = (Relation) lfirst(lc);
+ relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +281,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,17 +352,40 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, false);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- else if (stmt->for_all_tables)
+
+ if (schemaidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ Assert(list_length(schemaidlist) > 0);
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of the command.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
@@ -318,13 +472,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -362,7 +522,7 @@ InvalidatePublicationRels(List *relids)
*/
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+ HeapTuple tup, List *tables)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
@@ -376,13 +536,18 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(tables) > 0);
+
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *pubschemas = GetPublicationSchemas(pubid);
- if (stmt->tableAction == DEFELEM_ADD)
+ RelSchemaIsMemberOfSchemaList(rels, pubschemas, false);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -390,6 +555,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PUBLICATION_PART_ROOT);
List *delrels = NIL;
ListCell *oldlc;
+ List *pubschemas = GetPublicationSchemas(pubid);
+
+ RelSchemaIsMemberOfSchemaList(rels, pubschemas, false);
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
@@ -400,10 +568,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +579,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +601,77 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, true);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ List *rels;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, true);
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -452,6 +679,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
Relation rel;
HeapTuple tup;
Form_pg_publication pubform;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
rel = table_open(PublicationRelationId, RowExclusiveLock);
@@ -471,10 +700,18 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
stmt->pubname);
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ if (schemaidlist)
+ AlterPublicationSchemas(stmt, rel, tup, schemaidlist);
+ if (relations)
+ AlterPublicationTables(stmt, rel, tup, relations);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -541,9 +778,61 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -557,16 +846,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -582,9 +870,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -617,9 +903,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -640,10 +924,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -660,8 +943,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -669,7 +951,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -681,6 +963,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -693,8 +1003,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -716,6 +1025,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..1d6b6b9869 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..f91b9963c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e308de170e..11afed7c26 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4808,7 +4808,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4821,9 +4821,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4939,16 +4939,6 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
-{
- PublicationTable *newnode = makeNode(PublicationTable);
-
- COPY_NODE_FIELD(relation);
-
- return newnode;
-}
-
/*
* copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
*
@@ -5863,9 +5853,6 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
- break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 99440b40be..14af6b9937 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2295,7 +2295,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2307,9 +2307,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3114,14 +3114,6 @@ _equalValue(const Value *a, const Value *b)
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
/*
* equal
* returns whether two nodes are equal
@@ -3870,9 +3862,6 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
- break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6a0f46505c..fb5129af74 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -257,6 +257,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -426,14 +427,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -555,6 +555,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,58 +9593,114 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES IN SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+pubobj_expr:
+ any_name
{
- $$ = (Node *) $3;
+ /* inheritance query, implicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = false;
+ $$ = n;
}
- | FOR ALL TABLES
+ | any_name '*'
{
- $$ = (Node *) makeInteger(true);
+ /* inheritance query, explicitly */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $1;
+ n->inh = true;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY any_name
+ {
+ /* no inheritance */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $2;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | ONLY '(' any_name ')'
+ {
+ /* no inheritance, SQL99-style syntax */
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = $3;
+ n->inh = false;
+ n->spl_rel_type_syn = true;
+ $$ = n;
+ }
+ | CURRENT_SCHEMA
+ {
+ PublicationObjSpec *n = makeNode(PublicationObjSpec);
+ n->name = list_make1(makeString("CURRENT_SCHEMA"));
+ n->inh = false;
+ n->spl_rel_type_syn = false;
+ $$ = n;
}
;
-publication_table_list:
- publication_table
- { $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_UNKNOWN;
+ $$->location = @1;
+ }
;
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
@@ -9655,6 +9713,11 @@ publication_table: relation_expr
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET ALL TABLES IN SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9665,28 +9728,28 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 561266aa3e..9ceb2d5e0f 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -111,10 +111,18 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 56d13ff022..bec18e978a 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -482,6 +482,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -490,7 +491,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 743e5aa4f3..6380fa4a7a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -341,6 +341,28 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ List *name; /* publication object name */
+ bool inh; /* expand rel by inheritance? recursively act
+ * on children? */
+ bool spl_rel_type_syn; /* true if it is special relation type
+ * syntax */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1805,6 +1827,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_REL_IN_NAMESPACE,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3624,18 +3647,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3647,10 +3664,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 423780652f..1f2745f2ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2045,9 +2047,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v26-0001-Made-the-existing-relation-cache-invalidation-an.patchtext/x-patch; charset=US-ASCII; name=v26-0001-Made-the-existing-relation-cache-invalidation-an.patchDownload
From 4eef197e45e0c960e0dd7be539f261826690b45b Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 17:20:53 +0530
Subject: [PATCH v26 1/6] Made the existing relation cache invalidation and
getting the relations based on the publication partition option for a
specified relation into a function.
Made the existing relation cache invalidation code into a function. Also
made getting the relations based on the publication partition option for a
specified relation into a function. This will be used in the later
"FOR ALL TABLES IN SCHEMA" implementation patch.
---
src/backend/catalog/pg_publication.c | 67 +++++++++++++++-----------
src/backend/commands/publicationcmds.c | 42 ++++++++--------
src/include/commands/publicationcmds.h | 5 ++
3 files changed, 67 insertions(+), 47 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index d6fddd6efe..10dfe96bb2 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -238,10 +238,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,32 +307,8 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
-
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
-
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
-
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
-
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ pubrel->prrelid);
}
systable_endscan(scan);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 30929da1f5..945df49078 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -45,9 +45,6 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
@@ -329,23 +326,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
List *relids = GetPublicationRelations(pubform->oid,
PUBLICATION_PART_ALL);
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
-
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
@@ -355,6 +336,27 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
InvokeObjectPostAlterHook(PublicationRelationId, pubform->oid, 0);
}
+/*
+ * Invalidate the relations.
+ */
+void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point it's
+ * cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
/*
* Add or remove table to/from publication.
*/
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index c98d519b29..77a299bb18 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -17,6 +17,10 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
@@ -25,5 +29,6 @@ extern void RemovePublicationRelById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern void InvalidatePublicationRels(List *relids);
#endif /* PUBLICATIONCMDS_H */
--
2.30.2
v26-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v26-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 881324143d5dd9764561cd4baeec7580788eaa36 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:43:18 +0530
Subject: [PATCH v26 3/6] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 +++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 192 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 32 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 346 insertions(+), 51 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 67be849829..a397571d3f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1630,9 +1630,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3960,21 +3964,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4125,6 +4133,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4212,6 +4308,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10479,6 +10613,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18734,6 +18871,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..2fedd296dd 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..953e1f52cf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6407,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6443,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6453,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6479,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 75b867685a..133a5a73a0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,26 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN
+ * SCHEMA <schema>, ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 1f2745f2ab..a8824880dc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2051,6 +2051,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v26-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v26-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From e76b3fd6e0bdb6231c1921dd0278377091d78d08 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v26 4/6] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 487 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 229 +++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
6 files changed, 918 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index e1b7e31458..00fbb236bf 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2300,6 +2300,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2336,6 +2345,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index cad1b374be..e47463ec42 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,93 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+ERROR: relation "testpub_nopk" is not part of the publication
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +181,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -261,18 +348,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -304,11 +394,404 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 04b34ee299..a3860091dd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -147,9 +180,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -157,12 +192,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -183,11 +218,201 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v26-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v26-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From b3226526b1fd1919624b600f27290cdffd5e0781 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v26 5/6] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 73 ++++++++++++++++++++++--
doc/src/sgml/ref/create_publication.sgml | 69 +++++++++++++++++++---
3 files changed, 200 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2f0def9b19..c18a90a691 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..b3cef9dafd 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,9 +21,12 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,21 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which all tables
+ in schema are part of the publication. The
+ <literal>SET ALL TABLES IN SCHEMA</literal> clause will replace the list of
+ all tables in schemas of the publication with the specified one. The
+ <literal>ADD ALL TABLES IN SCHEMA</literal> clause will add the list of all
+ tables in schemas to the publication and
+ <literal>DROP ALL TABLES IN SCHEMA</literal> clause will remove the list of
+ all tables in schemas from the publication. Note that adding
+ schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -63,12 +80,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
+ The <literal>ADD SCHEMA</literal> and <literal>SET SCHEMA</literal> to a
+ publication requires the invoking user to be a superuser.
To alter the owner, you must also be a direct or indirect member of the new
owning role. The new owner must have <literal>CREATE</literal> privilege on
the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
publication must be a superuser. However, a superuser can change the
ownership of a publication regardless of these restrictions.
</para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
+ </para>
</refsect1>
<refsect1>
@@ -97,6 +126,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +179,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ ADD some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..aaced75dde 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | [ FOR { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ], }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ] } ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +87,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of schema specified in
+ <literal>FOR ALL TABLES IN SCHEMA</literal> option is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +105,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with schema's table specified as part of
+ <literal>FOR TABLE</literal> option is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +181,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +200,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +252,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v26-0006-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v26-0006-Implemented-pg_publication_objects-view.patchDownload
From 574e6ea733e21c0415636bac40d2d74c3a43b853 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v26 6/6] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c18a90a691..38293cbdba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Mon, Sep 6, 2021 at 6:56 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
From Thur, Sep 2, 2021 2:33 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Sep 1, 2021 at 6:58 AM houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com> wrote:
Here are some other comments for v23-000x patches.
3)+ .description =
"PUBLICATION SCHEMA",
+ .section =
SECTION_POST_DATA,
+ .createStmt + = query->data));Is it better to use something like 'PUBLICATION TABLES IN SCHEMA' to
describe the schema level table publication ? Because there could be
some other type publication such as 'ALL SEQUENCES IN SCHEMA' in the
future, it will be better to make it clear that we only publish table in schema inthis patch.
Modified
Thanks for updating the patch.
I think we might also need to mention the publication object 'table' in the
following types:1) + /* OCLASS_PUBLICATION_SCHEMA */ + { + "publication schema", OBJECT_PUBLICATION_SCHEMA + },2) + PUBLICATIONOBJ_SCHEMA, /* Schema type */ + PUBLICATIONOBJ_UNKNOWN /* Unknown type */ +} PublicationObjSpecType;3)
+ DO_PUBLICATION_SCHEMA,I think it might be to change the typename like XX_REL_IN_SCHEMA,
and adjust the comments.
Thanks for the comments, this is handled in the v26 patch attached at [1]/messages/by-id/CALDaNm3EwAVma8n4YpV1+QWiccuVPxpqNfbbrUU3s3XTHcTXew@mail.gmail.com
[1]: /messages/by-id/CALDaNm3EwAVma8n4YpV1+QWiccuVPxpqNfbbrUU3s3XTHcTXew@mail.gmail.com
Regards,
Vignesh
On Mon, Aug 16, 2021 at 7:01 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Smith <smithpb2250@gmail.com> writes:
Then the question from Peter E. [2] "Why can't I have a publication
that publishes tables t1, t2, t3, *and* schemas s1, s2, s3." would
have an intuitive solution like:CREATE PUBLICATION pub1
FOR TABLE t1,t2,t3 AND
FOR ALL TABLES IN SCHEMA s1,s2,s3;That seems a bit awkward, since the existing precedent is
to use commas.
AFAICS, the closest to this proposal we have is Grant/Revoke syntax
where we can give privilege on individual objects and all objects in
the schema. Is that you are referring to existing precedent or
something else?
We shouldn't need more than one FOR noise-word,
either. So I was imagining syntax more like, say,CREATE PUBLICATION pub1 FOR
TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2,
SEQUENCE seq1,seq2, ALL SEQUENCES IN SCHEMA s3,s4;Abstractly it'd be
createpub := CREATE PUBLICATION pubname FOR cpitem [, ... ] [ WITH ... ]
cpitem := ALL TABLES |
TABLE name |
ALL TABLES IN SCHEMA name |
ALL SEQUENCES |
SEQUENCE name |
ALL SEQUENCES IN SCHEMA name |
nameThe grammar output would need some post-analysis to attribute the
right type to bare "name" items, but that doesn't seem difficult.
The current patch (v26-0002-Added-schema-level-support-for-publication
at [1]/messages/by-id/CALDaNm3EwAVma8n4YpV1+QWiccuVPxpqNfbbrUU3s3XTHcTXew@mail.gmail.com) implements this syntax in roughly the way you have proposed
here. But, one thing I find a bit awkward is how it needs to keep a
separate flag to distinguish between names of different objects for
the post-analysis phase. The reason is that in CREATE PUBLICATION
syntax [2]https://www.postgresql.org/docs/devel/sql-createpublication.html one could supply additional decorators like *, ONLY with
table name but the same shouldn't be allowed be with schema name or
other object names. Is that okay or do you have any better ideas about
the same?
OTOH, if we implement something like Grant/Revoke where we can give
privilege on individual objects and all objects in the schema but not
in the same statement then such special flags won't be required to
distinguish different object names and we can build something on the
lines of current "privilege_target:" in gram.y.
[1]: /messages/by-id/CALDaNm3EwAVma8n4YpV1+QWiccuVPxpqNfbbrUU3s3XTHcTXew@mail.gmail.com
[2]: https://www.postgresql.org/docs/devel/sql-createpublication.html
--
With Regards,
Amit Kapila.
From Wed, Sept 8, 2021 7:44 PM vignesh C <vignesh21@gmail.com> wrote:
Modified
Thanks for the comments, the attached v26 patch has the changes for the same.
Hi,
Thanks for updating the patch, I have a suggestion for the gram.y.
Currently, we have the following two members in PublicationObjSpec to distinguish
between names of different objects for the post-analysis phase.
bool inh;
bool spl_rel_type_syn;
I was thinking do we have another way to distinguish that which can make code
smaller. I tried serval approaches and found a possible better approach.
First, I refer to the design of Grant/Revoke syntax, it use two members
'targtype' and 'objtype' to mark different type. 'targtype' can be
ACL_TARGET_OBJECT(single target) or ACL_TARGET_ALL_IN_SCHEMA(schema level)
.'objtype' is the actual type which can be OBJECT_SEQUENCE or OBJECT_TABLE or
... . I think if we follow this way, the code could be cleaner.
Second, we can move the special relation expression into a separate rule
'speical_relation_expr' like the following, this can remove duplicate code
used by pubobj_expr.
------
relation_expr:
qualified_name
{}
| speical_relation_expr
{
$$ = $1;
}
;
speical_relation_expr:
qualified_name '*'
{}
| ONLY qualified_name
{}
| ONLY '(' qualified_name ')'
{}
;
------
Finally, the gram.y will look like the following.
Personally, the code looks cleaner in this approach.
-----
pubobj_expr:
any_name
{
/* inheritance query, implicitly */
$$ = makeNode(PublicationObjSpec);
$$->targettype = TARGETOBJ_UNKNOWN;
$$->object = $1;
}
| special_relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->targettype = TARGETOBJ_TABLE;
$$->object = $1;
}
| CURRENT_SCHEMA
{
$$ = makeNode(PublicationObjSpec);
$$->object = list_make1(makeString("CURRENT_SCHEMA"));
$$->targettype = TARGETOBJ_SCHEMA;
}
;
/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
PublicationObjSpec: TABLE pubobj_expr
{
$$ = $2;
$$->pubobjtype = PUBLICATIONOBJ_TABLE_LIST;
$$->location = @1;
}
| ALL TABLES IN_P SCHEMA pubobj_expr
{
$$ = $5;
$$->pubobjtype = PUBLICATIONOBJ_SCHEMA_LIST;
$$->location = @1;
}
| pubobj_expr
{
$$ = $1;
$$->pubobjtype = PUBLICATIONOBJ_UNKNOWN;
$$->location = @1;
}
;
Attach a diff patch based on v26-0002 patch, which change the corresponding
code based on the design above. Please have a look.
Best regards,
Hou zj
Attachments:
0001-refactor_patchapplication/octet-stream; name=0001-refactor_patchDownload
From 3ba0b5633f283b69d43846fba814c922df3ba16d Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@fujitsu.com>
Date: Fri, 10 Sep 2021 10:10:19 +0800
Subject: [PATCH] refactor
---
src/backend/commands/publicationcmds.c | 41 +++++++++------
src/backend/parser/gram.y | 69 +++++++++++---------------
src/include/nodes/parsenodes.h | 20 +++++---
3 files changed, 65 insertions(+), 65 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index c3f0a0d9bb..81c7aaeee8 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -172,34 +172,43 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
else
prevobjtype = pubobj->pubobjtype;
- if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE_LIST)
{
- RangeVar *rel;
+ if (pubobj->targettype == TARGETOBJ_TABLE)
+ *rels = lappend(*rels, (RangeVar *) pubobj->object);
+ else
+ {
+ RangeVar *rel;
- rel = makeRangeVarFromNameList(pubobj->name);
- rel->inh = pubobj->inh;
- rel->location = pubobj->location;
- *rels = lappend(*rels, rel);
+ rel = makeRangeVarFromNameList((List *) pubobj->object);
+ rel->inh = true;
+ rel->location = pubobj->location;
+ *rels = lappend(*rels, rel);
+ }
}
- else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_SCHEMA_LIST)
{
Oid schemaid;
+ List *name;
char *schemaname;
- if (list_length(pubobj->name) > 1)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(pubobj->name)),
- parser_errposition(pstate, pubobj->location)));
-
- if (pubobj->spl_rel_type_syn)
+ if (pubobj->targettype == TARGETOBJ_SCHEMA &&
+ pubobj->targettype == TARGETOBJ_UNKNOWN)
+ name = (List *) pubobj->object;
+ else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid schema name at or near"),
parser_errposition(pstate, pubobj->location));
- schemaname = strVal(linitial(pubobj->name));
+ if (list_length(name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ schemaname = strVal(linitial(name));
if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
{
List *search_path;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index fb5129af74..0b0522e3dd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -427,7 +427,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list pub_obj_list
+ drop_option_list pub_obj_list pubobj_name
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -518,6 +518,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> special_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -9629,64 +9630,44 @@ CreatePublicationStmt:
;
pubobj_expr:
- any_name
+ pubobj_name
{
/* inheritance query, implicitly */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $1;
- n->inh = true;
- n->spl_rel_type_syn = false;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->targettype = TARGETOBJ_UNKNOWN;
+ $$->object = $1;
}
- | any_name '*'
+ | special_relation_expr
{
- /* inheritance query, explicitly */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $1;
- n->inh = true;
- n->spl_rel_type_syn = true;
- $$ = n;
- }
- | ONLY any_name
- {
- /* no inheritance */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $2;
- n->inh = false;
- n->spl_rel_type_syn = true;
- $$ = n;
- }
- | ONLY '(' any_name ')'
- {
- /* no inheritance, SQL99-style syntax */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $3;
- n->inh = false;
- n->spl_rel_type_syn = true;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->targettype = TARGETOBJ_TABLE;
+ $$->object = $1;
}
| CURRENT_SCHEMA
{
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = list_make1(makeString("CURRENT_SCHEMA"));
- n->inh = false;
- n->spl_rel_type_syn = false;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = list_make1(makeString("CURRENT_SCHEMA"));
+ $$->targettype = TARGETOBJ_SCHEMA;
}
;
+
+pubobj_name: ColId { $$ = list_make1(makeString($1)); }
+ | ColId indirection { $$ = lcons(makeString($1), $2); }
+ ;
+
/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
PublicationObjSpec: TABLE pubobj_expr
{
$$ = $2;
- $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE_LIST;
$$->location = @1;
}
| ALL TABLES IN_P SCHEMA pubobj_expr
{
$$ = $5;
- $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->pubobjtype = PUBLICATIONOBJ_SCHEMA_LIST;
$$->location = @1;
}
| pubobj_expr
@@ -12493,7 +12474,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | special_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+special_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -12516,7 +12504,6 @@ relation_expr:
}
;
-
relation_expr_list:
relation_expr { $$ = list_make1($1); }
| relation_expr_list ',' relation_expr { $$ = lappend($1, $3); }
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6380fa4a7a..fbc4cfa148 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -346,20 +346,24 @@ typedef struct RoleSpec
*/
typedef enum PublicationObjSpecType
{
- PUBLICATIONOBJ_TABLE, /* Table type */
- PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
- PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+ PUBLICATIONOBJ_TABLE_LIST, /* Start flag of table list */
+ PUBLICATIONOBJ_SCHEMA_LIST, /* Start flag of schema list */
+ PUBLICATIONOBJ_UNKNOWN
} PublicationObjSpecType;
+typedef enum TargetObjType
+{
+ TARGETOBJ_TABLE, /* Table name */
+ TARGETOBJ_SCHEMA, /* Schema name */
+ TARGETOBJ_UNKNOWN /* Unknown type */
+} TargetObjType;
+
typedef struct PublicationObjSpec
{
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
- List *name; /* publication object name */
- bool inh; /* expand rel by inheritance? recursively act
- * on children? */
- bool spl_rel_type_syn; /* true if it is special relation type
- * syntax */
+ TargetObjType targettype; /* type of target object */
+ void *object; /* publication object name */
int location; /* token location, or -1 if unknown */
} PublicationObjSpec;
--
2.18.4
From Friday, September 10, 2021 10:33 AM Hou Zhijie<houzj.fnst@fujitsu.com> wrote:
From Wed, Sept 8, 2021 7:44 PM vignesh C <vignesh21@gmail.com> wrote:
Modified
Thanks for the comments, the attached v26 patch has the changes for thesame.
Hi,
Thanks for updating the patch, I have a suggestion for the gram.y.
Currently, we have the following two members in PublicationObjSpec to
distinguish
between names of different objects for the post-analysis phase.bool inh;
bool spl_rel_type_syn;I was thinking do we have another way to distinguish that which can make code
smaller. I tried serval approaches and found a possible better approach.First, I refer to the design of Grant/Revoke syntax, it use two members
'targtype' and 'objtype' to mark different type. 'targtype' can be
ACL_TARGET_OBJECT(single target) or ACL_TARGET_ALL_IN_SCHEMA(schema
level)
.'objtype' is the actual type which can be OBJECT_SEQUENCE or OBJECT_TABLE
or
... . I think if we follow this way, the code could be cleaner.Second, we can move the special relation expression into a separate rule
'speical_relation_expr' like the following, this can remove duplicate code
used by pubobj_expr.
------
relation_expr:
qualified_name
{}
| speical_relation_expr
{
$$ = $1;
}
;
speical_relation_expr:
qualified_name '*'
{}
| ONLY qualified_name
{}
| ONLY '(' qualified_name ')'
{}
;
------Finally, the gram.y will look like the following.
Personally, the code looks cleaner in this approach.
Besides, If we don't want to use a new flag to distinguish tablename and schemaname,
We can only check the NodeTag to distinguish the difference.
Attach two diff patches based on the latest schema patch
which change the code with a flag and without a flag.
Best regards,
Hou zj
Attachments:
refactor-with-flag_patchapplication/octet-stream; name=refactor-with-flag_patchDownload
From 3ba0b5633f283b69d43846fba814c922df3ba16d Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@fujitsu.com>
Date: Fri, 10 Sep 2021 10:10:19 +0800
Subject: [PATCH] refactor
---
src/backend/commands/publicationcmds.c | 41 +++++++++------
src/backend/parser/gram.y | 69 +++++++++++---------------
src/include/nodes/parsenodes.h | 20 +++++---
3 files changed, 65 insertions(+), 65 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index c3f0a0d9bb..81c7aaeee8 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -172,34 +172,43 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
else
prevobjtype = pubobj->pubobjtype;
- if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE_LIST)
{
- RangeVar *rel;
+ if (pubobj->targettype == TARGETOBJ_TABLE)
+ *rels = lappend(*rels, (RangeVar *) pubobj->object);
+ else
+ {
+ RangeVar *rel;
- rel = makeRangeVarFromNameList(pubobj->name);
- rel->inh = pubobj->inh;
- rel->location = pubobj->location;
- *rels = lappend(*rels, rel);
+ rel = makeRangeVarFromNameList((List *) pubobj->object);
+ rel->inh = true;
+ rel->location = pubobj->location;
+ *rels = lappend(*rels, rel);
+ }
}
- else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_SCHEMA_LIST)
{
Oid schemaid;
+ List *name;
char *schemaname;
- if (list_length(pubobj->name) > 1)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(pubobj->name)),
- parser_errposition(pstate, pubobj->location)));
-
- if (pubobj->spl_rel_type_syn)
+ if (pubobj->targettype == TARGETOBJ_SCHEMA ||
+ pubobj->targettype == TARGETOBJ_UNKNOWN)
+ name = (List *) pubobj->object;
+ else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid schema name at or near"),
parser_errposition(pstate, pubobj->location));
- schemaname = strVal(linitial(pubobj->name));
+ if (list_length(name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ schemaname = strVal(linitial(name));
if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
{
List *search_path;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index fb5129af74..0b0522e3dd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -427,7 +427,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list pub_obj_list
+ drop_option_list pub_obj_list pubobj_name
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -518,6 +518,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> special_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -9629,64 +9630,44 @@ CreatePublicationStmt:
;
pubobj_expr:
- any_name
+ pubobj_name
{
/* inheritance query, implicitly */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $1;
- n->inh = true;
- n->spl_rel_type_syn = false;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->targettype = TARGETOBJ_UNKNOWN;
+ $$->object = $1;
}
- | any_name '*'
+ | special_relation_expr
{
- /* inheritance query, explicitly */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $1;
- n->inh = true;
- n->spl_rel_type_syn = true;
- $$ = n;
- }
- | ONLY any_name
- {
- /* no inheritance */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $2;
- n->inh = false;
- n->spl_rel_type_syn = true;
- $$ = n;
- }
- | ONLY '(' any_name ')'
- {
- /* no inheritance, SQL99-style syntax */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $3;
- n->inh = false;
- n->spl_rel_type_syn = true;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->targettype = TARGETOBJ_TABLE;
+ $$->object = $1;
}
| CURRENT_SCHEMA
{
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = list_make1(makeString("CURRENT_SCHEMA"));
- n->inh = false;
- n->spl_rel_type_syn = false;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = list_make1(makeString("CURRENT_SCHEMA"));
+ $$->targettype = TARGETOBJ_SCHEMA;
}
;
+
+pubobj_name: ColId { $$ = list_make1(makeString($1)); }
+ | ColId indirection { $$ = lcons(makeString($1), $2); }
+ ;
+
/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
PublicationObjSpec: TABLE pubobj_expr
{
$$ = $2;
- $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE_LIST;
$$->location = @1;
}
| ALL TABLES IN_P SCHEMA pubobj_expr
{
$$ = $5;
- $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->pubobjtype = PUBLICATIONOBJ_SCHEMA_LIST;
$$->location = @1;
}
| pubobj_expr
@@ -12493,7 +12474,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | special_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+special_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -12516,7 +12504,6 @@ relation_expr:
}
;
-
relation_expr_list:
relation_expr { $$ = list_make1($1); }
| relation_expr_list ',' relation_expr { $$ = lappend($1, $3); }
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6380fa4a7a..fbc4cfa148 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -346,20 +346,24 @@ typedef struct RoleSpec
*/
typedef enum PublicationObjSpecType
{
- PUBLICATIONOBJ_TABLE, /* Table type */
- PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
- PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+ PUBLICATIONOBJ_TABLE_LIST, /* Start flag of table list */
+ PUBLICATIONOBJ_SCHEMA_LIST, /* Start flag of schema list */
+ PUBLICATIONOBJ_UNKNOWN
} PublicationObjSpecType;
+typedef enum TargetObjType
+{
+ TARGETOBJ_TABLE, /* Table name */
+ TARGETOBJ_SCHEMA, /* Schema name */
+ TARGETOBJ_UNKNOWN /* Unknown type */
+} TargetObjType;
+
typedef struct PublicationObjSpec
{
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
- List *name; /* publication object name */
- bool inh; /* expand rel by inheritance? recursively act
- * on children? */
- bool spl_rel_type_syn; /* true if it is special relation type
- * syntax */
+ TargetObjType targettype; /* type of target object */
+ void *object; /* publication object name */
int location; /* token location, or -1 if unknown */
} PublicationObjSpec;
--
2.18.4
refactor-without-flag_patchapplication/octet-stream; name=refactor-without-flag_patchDownload
From 61546a59613aa6b4561acccbfe00d1bbcaddd7f2 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Fri, 10 Sep 2021 11:04:37 +0800
Subject: [PATCH] refactor without flag
---
src/backend/commands/publicationcmds.c | 38 +++++++++++++--------
src/backend/parser/gram.y | 62 +++++++++++++---------------------
src/include/nodes/parsenodes.h | 8 ++---
3 files changed, 50 insertions(+), 58 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index c3f0a0d..9458b2c 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -165,7 +165,10 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
foreach(cell, pubobjspec_list)
{
+ Node *node;
+
pubobj = (PublicationObjSpec *) lfirst(cell);
+ node = (Node *) pubobj->object;
if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
pubobj->pubobjtype = prevobjtype;
@@ -174,32 +177,39 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- RangeVar *rel;
+ if (IsA(node, RangeVar))
+ *rels = lappend(*rels, (RangeVar *) node);
+ else if (IsA(node, List))
+ {
+ RangeVar *rel;
- rel = makeRangeVarFromNameList(pubobj->name);
- rel->inh = pubobj->inh;
- rel->location = pubobj->location;
- *rels = lappend(*rels, rel);
+ rel = makeRangeVarFromNameList((List *) node);
+ rel->inh = true;
+ rel->location = pubobj->location;
+ *rels = lappend(*rels, rel);
+ }
}
else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
{
Oid schemaid;
+ List *name;
char *schemaname;
- if (list_length(pubobj->name) > 1)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(pubobj->name)),
- parser_errposition(pstate, pubobj->location)));
-
- if (pubobj->spl_rel_type_syn)
+ if (!IsA(node, List))
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid schema name at or near"),
parser_errposition(pstate, pubobj->location));
- schemaname = strVal(linitial(pubobj->name));
+ name = (List *) node;
+ if (list_length(name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ schemaname = strVal(linitial(name));
if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
{
List *search_path;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index fb5129a..7a7095b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -427,7 +427,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list pub_obj_list
+ drop_option_list pub_obj_list pubobj_name
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -518,6 +518,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> special_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -9629,52 +9630,29 @@ CreatePublicationStmt:
;
pubobj_expr:
- any_name
+ pubobj_name
{
/* inheritance query, implicitly */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $1;
- n->inh = true;
- n->spl_rel_type_syn = false;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
}
- | any_name '*'
+ | special_relation_expr
{
- /* inheritance query, explicitly */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $1;
- n->inh = true;
- n->spl_rel_type_syn = true;
- $$ = n;
- }
- | ONLY any_name
- {
- /* no inheritance */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $2;
- n->inh = false;
- n->spl_rel_type_syn = true;
- $$ = n;
- }
- | ONLY '(' any_name ')'
- {
- /* no inheritance, SQL99-style syntax */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $3;
- n->inh = false;
- n->spl_rel_type_syn = true;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
}
| CURRENT_SCHEMA
{
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = list_make1(makeString("CURRENT_SCHEMA"));
- n->inh = false;
- n->spl_rel_type_syn = false;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = list_make1(makeString("CURRENT_SCHEMA"));
}
;
+
+pubobj_name: ColId { $$ = list_make1(makeString($1)); }
+ | ColId indirection { $$ = lcons(makeString($1), $2); }
+ ;
+
/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
PublicationObjSpec: TABLE pubobj_expr
{
@@ -12493,7 +12471,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | special_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+special_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -12516,7 +12501,6 @@ relation_expr:
}
;
-
relation_expr_list:
relation_expr { $$ = list_make1($1); }
| relation_expr_list ',' relation_expr { $$ = lappend($1, $3); }
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6380fa4..5ad0c31 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -355,11 +355,9 @@ typedef struct PublicationObjSpec
{
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
- List *name; /* publication object name */
- bool inh; /* expand rel by inheritance? recursively act
- * on children? */
- bool spl_rel_type_syn; /* true if it is special relation type
- * syntax */
+ void *object; /* publication object could be:
+ RangeVar - table object
+ List - tablename or schemaname */
int location; /* token location, or -1 if unknown */
} PublicationObjSpec;
--
2.7.2.windows.1
On Fri, Sep 10, 2021 at 8:54 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
From Friday, September 10, 2021 10:33 AM Hou Zhijie<houzj.fnst@fujitsu.com> wrote:
Besides, If we don't want to use a new flag to distinguish tablename and schemaname,
We can only check the NodeTag to distinguish the difference.Attach two diff patches based on the latest schema patch
which change the code with a flag and without a flag.
I would prefer a version without additional flags unless you think it
is difficult to extend it in the future for other objects like
sequences which as far as I can see shouldn't be the case. Is there a
reason to define pubobj_name similar to any_name? If so, then please
do add the comments. One reason I could think of is that any_name is
not used for schema names currently which might have motivated you to
define a separate naming convention for publication.
--
With Regards,
Amit Kapila.
From Friday, September 10, 2021 1:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Sep 10, 2021 at 8:54 AM Hou zhijie <houzj.fnst@fujitsu.com> wrote:
From Friday, September 10, 2021 10:33 AM Hou
Zhijie<houzj.fnst@fujitsu.com> wrote:
Besides, If we don't want to use a new flag to distinguish tablename and
schemaname,
We can only check the NodeTag to distinguish the difference.
Attach two diff patches based on the latest schema patch
which change the code with a flag and without a flag.I would prefer a version without additional flags unless you think it
is difficult to extend it in the future for other objects like
sequences which as far as I can see shouldn't be the case.
Ok, I agreed.
Is there a
reason to define pubobj_name similar to any_name? If so, then please
do add the comments. One reason I could think of is that any_name is
not used for schema names currently which might have motivated you to
define a separate naming convention for publication.
When I used any_name, Bison reported that the dot('.') in rule attr
would have a shift/reduce conflict with the dot('.') in rule indirection_el
which also used in pubobj_expr. So, I declared a new rule which will directly
use indirection_el to resolve the conflicts.
Attach the without-flag version and add comments about the pubobj_name.
Best regards,
Hou zj
Attachments:
refactor-without-flag_patchapplication/octet-stream; name=refactor-without-flag_patchDownload
From 4cf251ee52ee61019fbdf34d968c6bc6405936ec Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Fri, 10 Sep 2021 13:35:17 +0800
Subject: [PATCH] refactor-without-flag
---
src/backend/commands/publicationcmds.c | 38 ++++++++++++-------
src/backend/parser/gram.y | 67 ++++++++++++++--------------------
src/include/nodes/parsenodes.h | 8 ++--
3 files changed, 55 insertions(+), 58 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index c3f0a0d..9458b2c 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -165,7 +165,10 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
foreach(cell, pubobjspec_list)
{
+ Node *node;
+
pubobj = (PublicationObjSpec *) lfirst(cell);
+ node = (Node *) pubobj->object;
if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
pubobj->pubobjtype = prevobjtype;
@@ -174,32 +177,39 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- RangeVar *rel;
+ if (IsA(node, RangeVar))
+ *rels = lappend(*rels, (RangeVar *) node);
+ else if (IsA(node, List))
+ {
+ RangeVar *rel;
- rel = makeRangeVarFromNameList(pubobj->name);
- rel->inh = pubobj->inh;
- rel->location = pubobj->location;
- *rels = lappend(*rels, rel);
+ rel = makeRangeVarFromNameList((List *) node);
+ rel->inh = true;
+ rel->location = pubobj->location;
+ *rels = lappend(*rels, rel);
+ }
}
else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
{
Oid schemaid;
+ List *name;
char *schemaname;
- if (list_length(pubobj->name) > 1)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(pubobj->name)),
- parser_errposition(pstate, pubobj->location)));
-
- if (pubobj->spl_rel_type_syn)
+ if (!IsA(node, List))
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid schema name at or near"),
parser_errposition(pstate, pubobj->location));
- schemaname = strVal(linitial(pubobj->name));
+ name = (List *) node;
+ if (list_length(name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ schemaname = strVal(linitial(name));
if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
{
List *search_path;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index fb5129a..56c3e56 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -427,7 +427,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list pub_obj_list
+ drop_option_list pub_obj_list pubobj_name
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -518,6 +518,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> special_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -9629,52 +9630,34 @@ CreatePublicationStmt:
;
pubobj_expr:
- any_name
+ pubobj_name
{
/* inheritance query, implicitly */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $1;
- n->inh = true;
- n->spl_rel_type_syn = false;
- $$ = n;
- }
- | any_name '*'
- {
- /* inheritance query, explicitly */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $1;
- n->inh = true;
- n->spl_rel_type_syn = true;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
}
- | ONLY any_name
+ | special_relation_expr
{
- /* no inheritance */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $2;
- n->inh = false;
- n->spl_rel_type_syn = true;
- $$ = n;
- }
- | ONLY '(' any_name ')'
- {
- /* no inheritance, SQL99-style syntax */
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = $3;
- n->inh = false;
- n->spl_rel_type_syn = true;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
}
| CURRENT_SCHEMA
{
- PublicationObjSpec *n = makeNode(PublicationObjSpec);
- n->name = list_make1(makeString("CURRENT_SCHEMA"));
- n->inh = false;
- n->spl_rel_type_syn = false;
- $$ = n;
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = list_make1(makeString("CURRENT_SCHEMA"));
}
;
+/*
+ * We cannot use any_name in pubobj_expr, because the dot('.') in rule attr
+ * would have a shift/reduce conflict with the dot('.') in rule indirection_el
+ * which also used in pubobj_expr. To resolve the conflicts, declare a new rule
+ * pubobj_name which directly use indirection_el.
+ */
+pubobj_name: ColId { $$ = list_make1(makeString($1)); }
+ | ColId indirection { $$ = lcons(makeString($1), $2); }
+ ;
+
/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
PublicationObjSpec: TABLE pubobj_expr
{
@@ -12493,7 +12476,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | special_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+special_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -12516,7 +12506,6 @@ relation_expr:
}
;
-
relation_expr_list:
relation_expr { $$ = list_make1($1); }
| relation_expr_list ',' relation_expr { $$ = lappend($1, $3); }
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6380fa4..5ad0c31 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -355,11 +355,9 @@ typedef struct PublicationObjSpec
{
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
- List *name; /* publication object name */
- bool inh; /* expand rel by inheritance? recursively act
- * on children? */
- bool spl_rel_type_syn; /* true if it is special relation type
- * syntax */
+ void *object; /* publication object could be:
+ RangeVar - table object
+ List - tablename or schemaname */
int location; /* token location, or -1 if unknown */
} PublicationObjSpec;
--
2.7.2.windows.1
On Fri, Sep 10, 2021 at 11:21 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
From Friday, September 10, 2021 1:10 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Sep 10, 2021 at 8:54 AM Hou zhijie <houzj.fnst@fujitsu.com> wrote:
From Friday, September 10, 2021 10:33 AM Hou
Zhijie<houzj.fnst@fujitsu.com> wrote:
Besides, If we don't want to use a new flag to distinguish tablename and
schemaname,
We can only check the NodeTag to distinguish the difference.
Attach two diff patches based on the latest schema patch
which change the code with a flag and without a flag.I would prefer a version without additional flags unless you think it
is difficult to extend it in the future for other objects like
sequences which as far as I can see shouldn't be the case.Ok, I agreed.
Is there a
reason to define pubobj_name similar to any_name? If so, then please
do add the comments. One reason I could think of is that any_name is
not used for schema names currently which might have motivated you to
define a separate naming convention for publication.When I used any_name, Bison reported that the dot('.') in rule attr
would have a shift/reduce conflict with the dot('.') in rule indirection_el
which also used in pubobj_expr. So, I declared a new rule which will directly
use indirection_el to resolve the conflicts.Attach the without-flag version and add comments about the pubobj_name.
Thanks for the changes, the suggested changes make the parsing code
simpler. I have merged the changes to the main patch. Attached v27
patch has the changes for the same.
Regards,
Vignesh
Attachments:
v27-0001-Made-the-existing-relation-cache-invalidation-an.patchtext/x-patch; charset=US-ASCII; name=v27-0001-Made-the-existing-relation-cache-invalidation-an.patchDownload
From aafdc80f9bce78d7dd5a707187eeeccb28fa846e Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 17:20:53 +0530
Subject: [PATCH v27 1/6] Made the existing relation cache invalidation and
getting the relations based on the publication partition option for a
specified relation into a function.
Made the existing relation cache invalidation code into a function. Also
made getting the relations based on the publication partition option for a
specified relation into a function. This will be used in the later
"FOR ALL TABLES IN SCHEMA" implementation patch.
---
src/backend/catalog/pg_publication.c | 67 +++++++++++++++-----------
src/backend/commands/publicationcmds.c | 42 ++++++++--------
src/include/commands/publicationcmds.h | 5 ++
3 files changed, 67 insertions(+), 47 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index d6fddd6efe..10dfe96bb2 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -238,10 +238,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,32 +307,8 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
-
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
-
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
-
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
-
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ pubrel->prrelid);
}
systable_endscan(scan);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 30929da1f5..945df49078 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -45,9 +45,6 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
@@ -329,23 +326,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
List *relids = GetPublicationRelations(pubform->oid,
PUBLICATION_PART_ALL);
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
-
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
@@ -355,6 +336,27 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
InvokeObjectPostAlterHook(PublicationRelationId, pubform->oid, 0);
}
+/*
+ * Invalidate the relations.
+ */
+void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point it's
+ * cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
/*
* Add or remove table to/from publication.
*/
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index c98d519b29..77a299bb18 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -17,6 +17,10 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
@@ -25,5 +29,6 @@ extern void RemovePublicationRelById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern void InvalidatePublicationRels(List *relids);
#endif /* PUBLICATIONCMDS_H */
--
2.30.2
v27-0002-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v27-0002-Added-schema-level-support-for-publication.patchDownload
From 4b9adab13f2312a60ae9be6ec29ce14eaa2c3179 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 17:01:27 +0530
Subject: [PATCH v27 2/6] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 150 ++++++
src/backend/catalog/pg_publication.c | 299 +++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 449 ++++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 19 +-
src/backend/nodes/equalfuncs.c | 17 +-
src/backend/parser/gram.y | 144 ++++--
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 10 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 36 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1105 insertions(+), 152 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..5a0d4a8232 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..2c785e9a5e 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..ac23bf87c3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_REL_IN_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1125,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1946,50 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication tables in schema. The first
+ * element of the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2262,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2355,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2905,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3903,6 +4009,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4473,6 +4595,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5712,6 +5838,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 10dfe96bb2..c59fef202b 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -75,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -141,14 +167,14 @@ pg_relation_is_publishable(PG_FUNCTION_ARGS)
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -172,10 +198,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -209,7 +235,85 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
table_close(rel, RowExclusiveLock);
/* Invalidate relcache so that publication info is rebuilt. */
- CacheInvalidateRelcache(targetrel->relation);
+ CacheInvalidateRelcache(targetrel);
+
+ return myself;
+}
+
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
return myself;
}
@@ -253,7 +357,7 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -263,14 +367,14 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -317,6 +421,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -355,7 +526,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -417,6 +588,100 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(schemaid != InvalidOid);
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -546,10 +811,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..d3c13ce5ce 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 945df49078..9458b2c8d9 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,12 +36,12 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -50,6 +52,10 @@ static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +141,139 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_UNKNOWN;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ Node *node;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+ node = (Node *) pubobj->object;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ if (IsA(node, RangeVar))
+ *rels = lappend(*rels, (RangeVar *) node);
+ else if (IsA(node, List))
+ {
+ RangeVar *rel;
+
+ rel = makeRangeVarFromNameList((List *) node);
+ rel->inh = true;
+ rel->location = pubobj->location;
+ *rels = lappend(*rels, rel);
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ Oid schemaid;
+ List *name;
+ char *schemaname;
+
+ if (!IsA(node, List))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ name = (List *) node;
+ if (list_length(name) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(name)),
+ parser_errposition(pstate, pubobj->location)));
+
+ schemaname = strVal(linitial(name));
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+ }
+ else
+ schemaid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
+ }
+}
+
+/*
+ * Check if the relation schema is member of the schema list.
+ */
+static void
+RelSchemaIsMemberOfSchemaList(List *rels, List *schemaidlist, bool schemacheck)
+{
+ ListCell *lc;
+ Oid relSchemaId;
+
+ foreach(lc, rels)
+ {
+ if (schemacheck)
+ {
+ Oid tableid = (Oid) lfirst_oid(lc);
+ relSchemaId = get_rel_namespace(tableid);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(get_rel_namespace(tableid))),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ get_rel_name(tableid),
+ get_namespace_name(get_rel_namespace(tableid))));
+ }
+ else
+ {
+ Relation rel = (Relation) lfirst(lc);
+ relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +291,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,17 +362,40 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, false);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- else if (stmt->for_all_tables)
+
+ if (schemaidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ Assert(list_length(schemaidlist) > 0);
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of the command.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
@@ -318,13 +482,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -362,7 +532,7 @@ InvalidatePublicationRels(List *relids)
*/
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+ HeapTuple tup, List *tables)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
@@ -376,13 +546,18 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(tables) > 0);
+
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *pubschemas = GetPublicationSchemas(pubid);
- if (stmt->tableAction == DEFELEM_ADD)
+ RelSchemaIsMemberOfSchemaList(rels, pubschemas, false);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -390,6 +565,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
PUBLICATION_PART_ROOT);
List *delrels = NIL;
ListCell *oldlc;
+ List *pubschemas = GetPublicationSchemas(pubid);
+
+ RelSchemaIsMemberOfSchemaList(rels, pubschemas, false);
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
@@ -400,10 +578,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +589,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +611,77 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, true);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ List *rels;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, true);
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -452,6 +689,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
Relation rel;
HeapTuple tup;
Form_pg_publication pubform;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
rel = table_open(PublicationRelationId, RowExclusiveLock);
@@ -471,10 +710,18 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
stmt->pubname);
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ if (schemaidlist)
+ AlterPublicationSchemas(stmt, rel, tup, schemaidlist);
+ if (relations)
+ AlterPublicationTables(stmt, rel, tup, relations);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -541,9 +788,61 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -557,16 +856,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -582,9 +880,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -617,9 +913,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -640,10 +934,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -660,8 +953,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -669,7 +961,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -681,6 +973,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -693,8 +1013,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -716,6 +1035,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..1d6b6b9869 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..f91b9963c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 83ec2a369e..d6d056889f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4813,7 +4813,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4826,9 +4826,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4955,16 +4955,6 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
-{
- PublicationTable *newnode = makeNode(PublicationTable);
-
- COPY_NODE_FIELD(relation);
-
- return newnode;
-}
-
/*
* copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
*
@@ -5884,9 +5874,6 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
- break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4bad709f83..f5a429e652 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2295,7 +2295,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2307,9 +2307,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3126,14 +3126,6 @@ _equalBitString(const BitString *a, const BitString *b)
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
/*
* equal
* returns whether two nodes are equal
@@ -3887,9 +3879,6 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
- break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..9e91f1abde 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -256,6 +256,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +426,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list pubobj_name
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +517,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> special_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -554,6 +555,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,58 +9594,96 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
*
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR TABLE [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES IN SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+pubobj_expr:
+ pubobj_name
+ {
+ /* inheritance query, implicitly */
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
+ }
+ | special_relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
}
- | FOR ALL TABLES
+ | CURRENT_SCHEMA
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = list_make1(makeString("CURRENT_SCHEMA"));
}
;
-publication_table_list:
- publication_table
- { $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
+/*
+ * We cannot use any_name in pubobj_expr, because the dot('.') in rule attr
+ * would have a shift/reduce conflict with the dot('.') in rule indirection_el
+ * which also used in pubobj_expr. To resolve the conflicts, declare a new rule
+ * pubobj_name which directly use indirection_el.
+ */
+pubobj_name: ColId { $$ = list_make1(makeString($1)); }
+ | ColId indirection { $$ = lcons(makeString($1), $2); }
;
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_UNKNOWN;
+ $$->location = @1;
+ }
+ ;
+
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
@@ -9655,6 +9696,11 @@ publication_table: relation_expr
*
* ALTER PUBLICATION name SET TABLE table [, table2]
*
+ * ALTER PUBLICATION name ADD ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name DROP ALL TABLES IN SCHEMA schema [, schema2]
+ *
+ * ALTER PUBLICATION name SET ALL TABLES IN SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
@@ -9665,28 +9711,28 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12476,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | special_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+special_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -12453,7 +12506,6 @@ relation_expr:
}
;
-
relation_expr_list:
relation_expr { $$ = list_make1($1); }
| relation_expr_list ',' relation_expr { $$ = lappend($1, $3); }
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 561266aa3e..9ceb2d5e0f 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -111,10 +111,18 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a692eb7b09..d34f61ffea 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -480,6 +480,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -488,7 +489,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 45e4f2a16e..5d0b2519ff 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ void *object; /* publication object could be:
+ * RangeVar - table object
+ * List - tablename or schemaname */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1817,6 +1837,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_REL_IN_NAMESPACE,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 423780652f..1f2745f2ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2045,9 +2047,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v27-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v27-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 71aea44fe8adfad282fdea4aef259825e68bfb5f Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v27 3/6] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 +++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 192 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 32 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 346 insertions(+), 51 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..93ed3344d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4126,6 +4134,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4213,6 +4309,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10619,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18854,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..2fedd296dd 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..953e1f52cf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6407,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6443,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6453,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6479,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..2cb4707e5b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,26 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN
+ * SCHEMA <schema>, ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 1f2745f2ab..a8824880dc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2051,6 +2051,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v27-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v27-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From e50285a1a390e35126db9cbf5e611fe0ac4d42a6 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v27 4/6] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 487 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 229 +++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
6 files changed, 918 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index cad1b374be..e47463ec42 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,93 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+ERROR: relation "testpub_nopk" is not part of the publication
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +181,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -261,18 +348,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -304,11 +394,404 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 04b34ee299..a3860091dd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -147,9 +180,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -157,12 +192,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -183,11 +218,201 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v27-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v27-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 05fe4e63d41373f38c4abf1bb104f01aa8b583b2 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v27 5/6] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 73 ++++++++++++++++++++++--
doc/src/sgml/ref/create_publication.sgml | 69 +++++++++++++++++++---
3 files changed, 200 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2f0def9b19..c18a90a691 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..b3cef9dafd 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,9 +21,12 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -50,7 +53,21 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The fourth variant of this command listed in the synopsis can change
+ The fourth, fifth and sixth variants of this command change which all tables
+ in schema are part of the publication. The
+ <literal>SET ALL TABLES IN SCHEMA</literal> clause will replace the list of
+ all tables in schemas of the publication with the specified one. The
+ <literal>ADD ALL TABLES IN SCHEMA</literal> clause will add the list of all
+ tables in schemas to the publication and
+ <literal>DROP ALL TABLES IN SCHEMA</literal> clause will remove the list of
+ all tables in schemas from the publication. Note that adding
+ schemas to a publication that is already subscribed to will require
+ a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on
+ the subscribing side in order to become effective.
+ </para>
+
+ <para>
+ The seventh variant of this command listed in the synopsis can change
all of the publication properties specified in
<xref linkend="sql-createpublication"/>. Properties not mentioned in the
command retain their previous settings.
@@ -63,12 +80,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
+ The <literal>ADD SCHEMA</literal> and <literal>SET SCHEMA</literal> to a
+ publication requires the invoking user to be a superuser.
To alter the owner, you must also be a direct or indirect member of the new
owning role. The new owner must have <literal>CREATE</literal> privilege on
the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
publication must be a superuser. However, a superuser can change the
ownership of a publication regardless of these restrictions.
</para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
+ </para>
</refsect1>
<refsect1>
@@ -97,6 +126,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +179,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ ADD some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july;
+</programlisting>
+ </para>
+
+ <para>
+ Set schema to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_july;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..aaced75dde 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,8 +22,9 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | [ FOR { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ], }
+ | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ] } ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +87,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of schema specified in
+ <literal>FOR ALL TABLES IN SCHEMA</literal> option is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +105,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with schema's table specified as part of
+ <literal>FOR TABLE</literal> option is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +181,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +200,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +252,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v27-0006-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v27-0006-Implemented-pg_publication_objects-view.patchDownload
From 90e73ae35d1ecf8d73a5c2f7cc61ce428e6c2d74 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v27 6/6] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c18a90a691..38293cbdba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Sun, Sep 12, 2021 at 8:43 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the changes, the suggested changes make the parsing code
simpler. I have merged the changes to the main patch. Attached v27
patch has the changes for the same.
+pubobj_name: ColId { $$ = list_make1(makeString($1)); }
+ | ColId indirection { $$ = lcons(makeString($1), $2); }
;
I think "ColId indirection" should handle catalog and schema name as
we are doing in qualified_name. See attached (this can be applied atop
v27-0002). The one other improvement we can do here is to extract the
common code from qualified_name (especially for "ColId indirection")
and pubobj_name. What do you think?
--
With Regards,
Amit Kapila.
Attachments:
v28-0003-Include-catalog-name-and-schema-name-in-rangevar.patchapplication/octet-stream; name=v28-0003-Include-catalog-name-and-schema-name-in-rangevar.patchDownload
From 9826381494f21a0155fe7ab76c4cb5fd7f7ebadc Mon Sep 17 00:00:00 2001
From: Amit Kapila <akapila@postgresql.org>
Date: Mon, 13 Sep 2021 15:57:39 +0530
Subject: [PATCH v28 3/5] Include catalog name and schema name in rangevar.
---
src/backend/parser/gram.y | 49 ++++++++++++++++++++++++++++++++-------
1 file changed, 40 insertions(+), 9 deletions(-)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9e91f1abde..adebc632a3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -426,7 +426,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list pub_obj_list pubobj_name
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -611,6 +611,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
+%type <node> pubobj_name
/*
@@ -9629,6 +9630,10 @@ CreatePublicationStmt:
}
;
+/*
+ * This expression can either have relation or schema name, so we can't
+ * directly use relation_expr here.
+ */
pubobj_expr:
pubobj_name
{
@@ -9648,14 +9653,40 @@ pubobj_expr:
}
;
-/*
- * We cannot use any_name in pubobj_expr, because the dot('.') in rule attr
- * would have a shift/reduce conflict with the dot('.') in rule indirection_el
- * which also used in pubobj_expr. To resolve the conflicts, declare a new rule
- * pubobj_name which directly use indirection_el.
- */
-pubobj_name: ColId { $$ = list_make1(makeString($1)); }
- | ColId indirection { $$ = lcons(makeString($1), $2); }
+/* This can be either a schema or relation name. */
+pubobj_name:
+ ColId
+ {
+ $$ = (Node *) list_make1(makeString($1));
+ }
+ | ColId indirection
+ {
+ RangeVar *range_var;
+ check_qualified_name($2, yyscanner);
+ range_var = makeRangeVar(NULL, NULL, @1);
+ switch (list_length($2))
+ {
+ case 1:
+ range_var->catalogname = NULL;
+ range_var->schemaname = $1;
+ range_var->relname = strVal(linitial($2));
+ break;
+ case 2:
+ range_var->catalogname = $1;
+ range_var->schemaname = strVal(linitial($2));
+ range_var->relname = strVal(lsecond($2));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString($1), $2))),
+ parser_errposition(@1)));
+ break;
+ }
+
+ $$ = (Node *) range_var;
+ }
;
/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
--
2.28.0.windows.1
On Sunday, September 12, 2021 11:13 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the changes, the suggested changes make the parsing code
simpler. I have merged the changes to the main patch. Attached v27
patch has the changes for the same.
Thanks for your new patch. I had a look at the changes related to document and tried
the patch. Here are several comments.
1.
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
+ The <literal>ADD SCHEMA</literal> and <literal>SET SCHEMA</literal> to a
+ publication requires the invoking user to be a superuser.
To alter the owner, you must also be a direct or indirect member of the new
owning role. The new owner must have <literal>CREATE</literal> privilege on
the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
publication must be a superuser. However, a superuser can change the
ownership of a publication regardless of these restrictions.
</para>
ADD SCHEMA
->
ADD ALL TABLES IN SCHEMA
SET SCHEMA
->
SET ALL TABLES IN SCHEMA
2.
+ <para>
+ ADD some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
ADD some tables and schemas to the publication:
->
Add some tables and schemas to the publication:
3.
+ <para>
+ Drop some schema from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july;
+</programlisting>
+ </para>
Drop some schema from the publication:
->
Drop some schemas from the publication:
4.
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
There are two Spaces at the end of the paragraph.
5.
Adding a table and the schema where the table belonged to is not supported. But
it didn't report error message when I try to add them in the same statement by
using 'ALTER PUBLICATION'.
For example:
postgres=# create publication pub;
CREATE PUBLICATION
postgres=# alter publication pub add all tables in schema s1, table s1.tbl;
ALTER PUBLICATION
postgres=# \dRp+
Publication pub
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
----------+------------+---------+---------+---------+-----------+----------
postgres | f | t | t | t | t | f
Tables:
"s1.tbl"
Tables from schemas:
"s1"
It didn't check if table 's1.tbl' is member of schema 's1'.
6.
I think if I use 'ALTER PUBLICATION ... SET', both the list of tables and the
list of all tables in schemas should be reset. The publication should only
contain the tables and all tables in schemas which user specified. If user only
specified all tables in schema, and didn't specify tables, the tables which used
to be part of the publication should be dropped, too. But currently, if I didn't
specify tables, the list of tables wouldn't be set to empty. Thoughts?
For example:
postgres=# create publication pub for table s1.tbl;
CREATE PUBLICATION
postgres=# alter publication pub set all tables in schema s2;
ALTER PUBLICATION
postgres=# \dRp+
Publication pub
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
----------+------------+---------+---------+---------+-----------+----------
postgres | f | t | t | t | t | f
Tables:
"s1.tbl"
Tables from schemas:
"s2"
Regards
Tang
From Sun, Sept 12, 2021 11:13 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Sep 10, 2021 at 11:21 AM Hou Zhijie <houzj.fnst@fujitsu.com> wrote:
Attach the without-flag version and add comments about the pubobj_name.
Thanks for the changes, the suggested changes make the parsing code simpler.
I have merged the changes to the main patch. Attached v27 patch has the
changes for the same.
Hi,
I have some suggestions for the documents and comments of the new syntax.
IMO, the document could be clearer in the following format.
------
Synopsis
CREATE PUBLICATION name
[ FOR ALL TABLES
| FOR publication object [, ... ] ]
[ WITH ( publication_parameter [= value] [, ... ] ) ]
where publication object is one of:
TABLE [ ONLY ] table_name [ * ] [, ... ]
ALL TABLES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ]
------
Attach a diff(based on v27-*) which change the doc and comments like the
following.
Best regards,
Hou zj
Attachments:
0001-doc-diff.patchapplication/octet-stream; name=0001-doc-diff.patchDownload
From 081544a59478e9d44a0ec7b7d42e66c8dd0c268e Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Mon, 13 Sep 2021 09:57:36 +0800
Subject: [PATCH] doc diff
---
doc/src/sgml/ref/alter_publication.sgml | 14 ++++++++------
doc/src/sgml/ref/create_publication.sgml | 8 ++++++--
src/backend/parser/gram.y | 20 ++++++++++++--------
3 files changed, 26 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index b3cef9d..a2f5e6e 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,15 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
- | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
- | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] }
- | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ...] }
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index aaced75..0ca7e7c 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -23,9 +23,13 @@ PostgreSQL documentation
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
[ FOR ALL TABLES
- | [ FOR { TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ], }
- | { ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ] } ] ]
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9e91f1a..f7bf83e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9598,9 +9598,13 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
*
* CREATE PUBLICATION FOR ALL TABLES [WITH options]
*
- * CREATE PUBLICATION FOR TABLE [WITH options]
+ * CREATE PUBLICATION FOR pub_obj [, pub_obj2] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, table2]
+ * ALL TABLES IN SCHEMA schema [, schema2]
*
- * CREATE PUBLICATION FOR ALL TABLES IN SCHEMA [WITH options]
*****************************************************************************/
CreatePublicationStmt:
@@ -9690,17 +9694,17 @@ pub_obj_list: PublicationObjSpec
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, pub_obj2]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name DROP pub_obj [, pub_obj2]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, pub_obj2]
*
- * ALTER PUBLICATION name ADD ALL TABLES IN SCHEMA schema [, schema2]
+ * pub_obj is one of:
*
- * ALTER PUBLICATION name DROP ALL TABLES IN SCHEMA schema [, schema2]
+ * TABLE table [, table2]
+ * ALL TABLES IN SCHEMA schema [, schema2]
*
- * ALTER PUBLICATION name SET ALL TABLES IN SCHEMA schema [, schema2]
*****************************************************************************/
AlterPublicationStmt:
--
2.7.2.windows.1
On Mon, Sep 13, 2021 at 7:06 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
6.
I think if I use 'ALTER PUBLICATION ... SET', both the list of tables and the
list of all tables in schemas should be reset. The publication should only
contain the tables and all tables in schemas which user specified. If user only
specified all tables in schema, and didn't specify tables, the tables which used
to be part of the publication should be dropped, too. But currently, if I didn't
specify tables, the list of tables wouldn't be set to empty. Thoughts?
I think we can go either way here but it seems like we should drop the
tables in the case you mentioned. The idea is that the SET variant in
ALTER PUBLICATION should replace the set of tables and schemas for the
publication which seems to be in line with the current behavior where
we replace the set of tables.
Anyone else wants to weigh in on this?
--
With Regards,
Amit Kapila.
Amit Kapila <amit.kapila16@gmail.com> writes:
On Mon, Sep 13, 2021 at 7:06 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:I think if I use 'ALTER PUBLICATION ... SET', both the list of tables and the
list of all tables in schemas should be reset. The publication should only
contain the tables and all tables in schemas which user specified. If user only
specified all tables in schema, and didn't specify tables, the tables which used
to be part of the publication should be dropped, too. But currently, if I didn't
specify tables, the list of tables wouldn't be set to empty. Thoughts?
I think we can go either way here but it seems like we should drop the
tables in the case you mentioned. The idea is that the SET variant in
ALTER PUBLICATION should replace the set of tables and schemas for the
publication which seems to be in line with the current behavior where
we replace the set of tables.
Yeah, I think it's sensible to define that there is just one SET variant
that replaces both the list-of-tables and the list-of-schemas. (Of
course, the syntax for it has to permit both lists to be written.)
You could imagine having two independent SET commands for the two lists,
but that seems fairly confusing.
regards, tom lane
On Mon, Sep 13, 2021 at 5:11 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Sun, Sep 12, 2021 at 8:43 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the changes, the suggested changes make the parsing code
simpler. I have merged the changes to the main patch. Attached v27
patch has the changes for the same.+pubobj_name: ColId { $$ = list_make1(makeString($1)); } + | ColId indirection { $$ = lcons(makeString($1), $2); } ;I think "ColId indirection" should handle catalog and schema name as
we are doing in qualified_name. See attached (this can be applied atop
v27-0002). The one other improvement we can do here is to extract the
common code from qualified_name (especially for "ColId indirection")
and pubobj_name. What do you think?
One more suggestion for changes in gram.y:
@@ -12430,7 +12476,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | special_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+special_relation_expr:
+ qualified_name '*'
I am not sure if naming the above rule as special_relation_expr is a
good idea. Can we name it as extended_relation_expr?
--
With Regards,
Amit Kapila.
From Tue, Sept 14, 2021 11:53 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Sep 13, 2021 at 7:06 PM tanghy.fnst@fujitsu.com <tanghy.fnst@fujitsu.com> wrote:
6.
I think if I use 'ALTER PUBLICATION ... SET', both the list of tables
and the list of all tables in schemas should be reset. The publication
should only contain the tables and all tables in schemas which user
specified. If user only specified all tables in schema, and didn't
specify tables, the tables which used to be part of the publication
should be dropped, too. But currently, if I didn't specify tables, the list oftables wouldn't be set to empty. Thoughts?
I think we can go either way here but it seems like we should drop the tables in
the case you mentioned. The idea is that the SET variant in ALTER PUBLICATION
should replace the set of tables and schemas for the publication which seems
to be in line with the current behavior where we replace the set of tables.Anyone else wants to weigh in on this?
I agree that the one SET variant should replaces both the list-of-tables and the list-of-schemas.
Best regards,
Hou zj
On Mon, Sep 13, 2021 at 5:11 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Sun, Sep 12, 2021 at 8:43 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the changes, the suggested changes make the parsing code
simpler. I have merged the changes to the main patch. Attached v27
patch has the changes for the same.+pubobj_name: ColId { $$ = list_make1(makeString($1)); } + | ColId indirection { $$ = lcons(makeString($1), $2); } ;I think "ColId indirection" should handle catalog and schema name as
we are doing in qualified_name. See attached (this can be applied atop
v27-0002). The one other improvement we can do here is to extract the
common code from qualified_name (especially for "ColId indirection")
and pubobj_name. What do you think?
I have handled this in the patch attached.
Regards,
Vignesh
Attachments:
v28-0001-Made-the-existing-relation-cache-invalidation-an.patchtext/x-patch; charset=US-ASCII; name=v28-0001-Made-the-existing-relation-cache-invalidation-an.patchDownload
From 042c396d41453ff2345a589d197217654e323606 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 17:20:53 +0530
Subject: [PATCH v28 1/6] Made the existing relation cache invalidation and
getting the relations based on the publication partition option for a
specified relation into a function.
Made the existing relation cache invalidation code into a function. Also
made getting the relations based on the publication partition option for a
specified relation into a function. This will be used in the later
"FOR ALL TABLES IN SCHEMA" implementation patch.
---
src/backend/catalog/pg_publication.c | 67 +++++++++++++++-----------
src/backend/commands/publicationcmds.c | 42 ++++++++--------
src/include/commands/publicationcmds.h | 5 ++
3 files changed, 67 insertions(+), 47 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index d6fddd6efe..10dfe96bb2 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -238,10 +238,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,32 +307,8 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
-
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
-
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
-
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
-
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ pubrel->prrelid);
}
systable_endscan(scan);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 30929da1f5..945df49078 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -45,9 +45,6 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
@@ -329,23 +326,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
List *relids = GetPublicationRelations(pubform->oid,
PUBLICATION_PART_ALL);
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
-
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
@@ -355,6 +336,27 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
InvokeObjectPostAlterHook(PublicationRelationId, pubform->oid, 0);
}
+/*
+ * Invalidate the relations.
+ */
+void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point it's
+ * cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
/*
* Add or remove table to/from publication.
*/
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index c98d519b29..77a299bb18 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -17,6 +17,10 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
@@ -25,5 +29,6 @@ extern void RemovePublicationRelById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern void InvalidatePublicationRels(List *relids);
#endif /* PUBLICATIONCMDS_H */
--
2.30.2
v28-0002-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v28-0002-Added-schema-level-support-for-publication.patchDownload
From f7999284fc10fdf56be4b30f321238416eaaadb4 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 17:01:27 +0530
Subject: [PATCH v28 2/6] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 150 ++++++
src/backend/catalog/pg_publication.c | 299 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 482 ++++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 19 +-
src/backend/nodes/equalfuncs.c | 17 +-
src/backend/parser/gram.y | 213 +++++---
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 10 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 36 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1178 insertions(+), 181 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..5a0d4a8232 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3428,6 +3428,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
@@ -3567,6 +3568,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..2c785e9a5e 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..ac23bf87c3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_REL_IN_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1118,6 +1125,10 @@ get_object_address(ObjectType objtype, Node *object,
&relation,
missing_ok);
break;
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_DEFACL:
address = get_object_address_defacl(castNode(List, object),
missing_ok);
@@ -1935,6 +1946,50 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication tables in schema. The first
+ * element of the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2207,6 +2262,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
if (list_length(args) != 1)
@@ -2299,6 +2355,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2905,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3903,6 +4009,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4473,6 +4595,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5712,6 +5838,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 10dfe96bb2..c59fef202b 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -75,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -141,14 +167,14 @@ pg_relation_is_publishable(PG_FUNCTION_ARGS)
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -172,10 +198,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -209,7 +235,85 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
table_close(rel, RowExclusiveLock);
/* Invalidate relcache so that publication info is rebuilt. */
- CacheInvalidateRelcache(targetrel->relation);
+ CacheInvalidateRelcache(targetrel);
+
+ return myself;
+}
+
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
return myself;
}
@@ -253,7 +357,7 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -263,14 +367,14 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -317,6 +421,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -355,7 +526,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -417,6 +588,100 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(schemaid != InvalidOid);
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -546,10 +811,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..d3c13ce5ce 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 945df49078..780fc6d000 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,12 +36,13 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -49,7 +52,12 @@ static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok,
+ bool is_oidtype);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,127 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_UNKNOWN;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ Node *node;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+ node = (Node *) pubobj->object;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_UNKNOWN)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ if (IsA(node, RangeVar))
+ *rels = lappend(*rels, (RangeVar *) node);
+ else if (IsA(node, String))
+ {
+ RangeVar *rel = makeRangeVar(NULL, strVal(node),
+ pubobj->location);
+ *rels = lappend(*rels, rel);
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ Oid schemaid;
+ char *schemaname;
+
+ if (!IsA(node, String))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(node);
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+ }
+ else
+ schemaid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
+ }
+}
+
+/*
+ * Check if the relation schema is member of the schema list.
+ */
+static void
+RelSchemaIsMemberOfSchemaList(List *rels, List *schemaidlist, bool schemacheck)
+{
+ ListCell *lc;
+ Oid relSchemaId;
+
+ foreach(lc, rels)
+ {
+ if (schemacheck)
+ {
+ Oid tableid = (Oid) lfirst_oid(lc);
+ relSchemaId = get_rel_namespace(tableid);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(get_rel_namespace(tableid))),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ get_rel_name(tableid),
+ get_namespace_name(get_rel_namespace(tableid))));
+ }
+ else
+ {
+ Relation rel = (Relation) lfirst(lc);
+ relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +281,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,17 +352,40 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, false);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- else if (stmt->for_all_tables)
+
+ if (schemaidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ Assert(list_length(schemaidlist) > 0);
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of the command.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
@@ -318,13 +472,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -362,7 +522,7 @@ InvalidatePublicationRels(List *relids)
*/
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+ HeapTuple tup, List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
@@ -376,14 +536,26 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(tables) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(tables);
- if (stmt->tableAction == DEFELEM_ADD)
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *pubschemas = GetPublicationSchemas(pubid);
+
+ /* check if the relation is member of the schema list specified */
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, false);
+
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication.
+ */
+ RelSchemaIsMemberOfSchemaList(rels, pubschemas, false);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
- PublicationDropTables(pubid, rels, false);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropTables(pubid, rels, false, false);
else /* DEFELEM_SET */
{
List *oldrelids = GetPublicationRelations(pubid,
@@ -391,6 +563,19 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ /* check if relation is member of the schema list specified */
+ if (schemaidlist)
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, false);
+ else
+ {
+ /*
+ * If ALL TABLES IN SCHEMA option was not specified remove the
+ * existing schemas from the publication.
+ */
+ List *pubschemas = GetPublicationSchemas(pubid);
+ PublicationDropSchemas(pubform->oid, pubschemas, false);
+ }
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +585,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,21 +596,15 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropTables(pubid, delrels, true, false);
/*
* Don't bother calculating the difference for adding, we'll catch and
@@ -440,11 +618,84 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, List *schemaidlist, List *tables)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, true);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ List *rels;
+
+ /*
+ * If the table option was not specified remove the existing tables
+ * from the publication.
+ */
+ if (!tables)
+ {
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ PublicationDropTables(pubform->oid, rels, false, true);
+ }
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -452,6 +703,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
Relation rel;
HeapTuple tup;
Form_pg_publication pubform;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
rel = table_open(PublicationRelationId, RowExclusiveLock);
@@ -471,10 +724,18 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
stmt->pubname);
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ if (relations)
+ AlterPublicationTables(stmt, rel, tup, relations, schemaidlist);
+ if (schemaidlist)
+ AlterPublicationSchemas(stmt, rel, tup, schemaidlist, relations);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -541,9 +802,61 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -557,16 +870,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -582,9 +894,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -617,9 +927,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -640,10 +948,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -660,8 +967,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -669,7 +975,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -681,11 +987,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropTables(Oid pubid, List *rels, bool missing_ok, bool is_oidtype)
{
ObjectAddress obj;
ListCell *lc;
@@ -693,9 +1027,15 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
- Oid relid = RelationGetRelid(rel);
+ Oid relid;
+
+ if (is_oidtype)
+ relid = lfirst_oid(lc);
+ else
+ {
+ Relation rel = (Relation) lfirst(lc);
+ relid = RelationGetRelid(rel);
+ }
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
ObjectIdGetDatum(relid),
@@ -708,7 +1048,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("relation \"%s\" is not part of the publication",
- RelationGetRelationName(rel))));
+ get_rel_name(relid))));
}
ObjectAddressSet(obj, PublicationRelRelationId, prid);
@@ -716,6 +1056,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..1d6b6b9869 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..f91b9963c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 83ec2a369e..d6d056889f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4813,7 +4813,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4826,9 +4826,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4955,16 +4955,6 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
-{
- PublicationTable *newnode = makeNode(PublicationTable);
-
- COPY_NODE_FIELD(relation);
-
- return newnode;
-}
-
/*
* copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
*
@@ -5884,9 +5874,6 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
- break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4bad709f83..f5a429e652 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2295,7 +2295,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2307,9 +2307,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3126,14 +3126,6 @@ _equalBitString(const BitString *a, const BitString *b)
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
/*
* equal
* returns whether two nodes are equal
@@ -3887,9 +3879,6 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
- break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..f7d7cab596 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,6 +195,9 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
@@ -256,6 +259,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +429,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +520,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -554,6 +558,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
+%type <node> pubobj_name
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,69 +9598,117 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, pub_obj2] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, table2]
+ * ALL TABLES IN SCHEMA schema [, schema2]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
+pubobj_expr:
+ pubobj_name
+ {
+ /* inheritance query, implicitly */
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
+ }
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = makeString("CURRENT_SCHEMA");
+ }
;
-publication_for_tables:
- FOR TABLE publication_table_list
+/* This can be either a schema or relation name. */
+pubobj_name:
+ ColId
{
- $$ = (Node *) $3;
+ $$ = (Node *) makeString($1);
}
- | FOR ALL TABLES
+ | ColId indirection
{
- $$ = (Node *) makeInteger(true);
+ $$ = (Node *) makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
-publication_table_list:
- publication_table
- { $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_UNKNOWN;
+ $$->location = @1;
+ }
;
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, pub_obj ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name DROP pub_obj [, pub_obj ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, pub_obj ...]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, table_name ...]
+ * ALL TABLES IN SCHEMA schema_name [, schema_name ...]
*
*****************************************************************************/
@@ -9665,28 +9720,28 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12485,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -12453,7 +12515,6 @@ relation_expr:
}
;
-
relation_expr_list:
relation_expr { $$ = list_make1($1); }
| relation_expr_list ',' relation_expr { $$ = lappend($1, $3); }
@@ -15104,28 +15165,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17045,6 +17085,41 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r = makeRangeVar(NULL, NULL, location);
+
+ check_qualified_name(namelist, yyscanner);
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 561266aa3e..9ceb2d5e0f 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -111,10 +111,18 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a692eb7b09..d34f61ffea 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -480,6 +480,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -488,7 +489,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 45e4f2a16e..f8b6cbd328 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_UNKNOWN /* Unknown type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ void *object; /* publication object could be:
+ * RangeVar - table object
+ * String - tablename or schemaname */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1817,6 +1837,7 @@ typedef enum ObjectType
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
+ OBJECT_PUBLICATION_REL_IN_NAMESPACE,
OBJECT_ROLE,
OBJECT_ROUTINE,
OBJECT_RULE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 423780652f..1f2745f2ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2045,9 +2047,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v28-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v28-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 5b5630d218b13cee9bbe0f1f235bae3f16d5e795 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v28 3/6] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 +++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 192 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 32 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 346 insertions(+), 51 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..93ed3344d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4126,6 +4134,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4213,6 +4309,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10619,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18854,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..2fedd296dd 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..953e1f52cf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6407,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6443,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6453,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6479,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..2cb4707e5b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,26 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN
+ * SCHEMA <schema>, ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 1f2745f2ab..a8824880dc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2051,6 +2051,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v28-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v28-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From af7d0ee40a4abdb380264967ffc94dff072351ef Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v28 4/6] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 483 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 229 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
6 files changed, 914 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index cad1b374be..037da6c793 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,89 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+ERROR: relation "testpub_nopk" is not part of the publication
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- 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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +177,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -261,18 +344,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -304,11 +390,404 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 04b34ee299..a3860091dd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -147,9 +180,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -157,12 +192,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -183,11 +218,201 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v28-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v28-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From ea5dd1b3498a38275d6d241822702aa69d3da535 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v28 5/6] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 89 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 +++++++++++++++++--
3 files changed, 208 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2f0def9b19..c18a90a691 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..af7c946498 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,17 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables and/or all tables in schema are
+ part of the publication. The <literal>SET</literal> clause will replace
+ the list of tables and/or all tables in schema in the publication with the
+ specified one, the existing tables and all tables in schema that were
+ present in the publication will be removed. The <literal>ADD</literal>
+ clause will add one or more tables and/or all tables in schema to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables and/or all tables in schema from the publication. Note that adding
+ tables and/or all tables in schema to a publication that is already
+ subscribed to will require a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal>
+ action on the subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +71,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +118,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +171,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..0ca7e7c0e6 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of schema specified in
+ <literal>FOR ALL TABLES IN SCHEMA</literal> option is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with schema's table specified as part of
+ <literal>FOR TABLE</literal> option is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v28-0006-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v28-0006-Implemented-pg_publication_objects-view.patchDownload
From 335cd5329546f62252914caa080f5f894a80e1fc Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v28 6/6] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c18a90a691..38293cbdba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Mon, Sep 13, 2021 at 7:06 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Sunday, September 12, 2021 11:13 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the changes, the suggested changes make the parsing code
simpler. I have merged the changes to the main patch. Attached v27
patch has the changes for the same.Thanks for your new patch. I had a look at the changes related to document and tried
the patch. Here are several comments.1. <para> You must own the publication to use <command>ALTER PUBLICATION</command>. Adding a table to a publication additionally requires owning that table. + The <literal>ADD SCHEMA</literal> and <literal>SET SCHEMA</literal> to a + publication requires the invoking user to be a superuser. To alter the owner, you must also be a direct or indirect member of the new owning role. The new owner must have <literal>CREATE</literal> privilege on the database. Also, the new owner of a <literal>FOR ALL TABLES</literal> publication must be a superuser. However, a superuser can change the ownership of a publication regardless of these restrictions. </para>ADD SCHEMA
->
ADD ALL TABLES IN SCHEMASET SCHEMA
->
SET ALL TABLES IN SCHEMA
Modified
2. + <para> + ADD some tables and schemas to the publication: +<programlisting> +ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production; +</programlisting> + </para>ADD some tables and schemas to the publication:
->
Add some tables and schemas to the publication:
Modified
3. + <para> + Drop some schema from the publication: +<programlisting> +ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july; +</programlisting> + </para>Drop some schema from the publication:
->
Drop some schemas from the publication:
Modified
4. + The catalog <structname>pg_publication_namespace</structname> contains the + mapping between schemas and publications in the database. This is a + many-to-many mapping.There are two Spaces at the end of the paragraph.
I felt this is ok, as both single space and double space are used at
various places.
5.
Adding a table and the schema where the table belonged to is not supported. But
it didn't report error message when I try to add them in the same statement by
using 'ALTER PUBLICATION'.For example:
postgres=# create publication pub;
CREATE PUBLICATION
postgres=# alter publication pub add all tables in schema s1, table s1.tbl;
ALTER PUBLICATION
postgres=# \dRp+
Publication pub
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
----------+------------+---------+---------+---------+-----------+----------
postgres | f | t | t | t | t | f
Tables:
"s1.tbl"
Tables from schemas:
"s1"It didn't check if table 's1.tbl' is member of schema 's1'.
Modified.
6.
I think if I use 'ALTER PUBLICATION ... SET', both the list of tables and the
list of all tables in schemas should be reset. The publication should only
contain the tables and all tables in schemas which user specified. If user only
specified all tables in schema, and didn't specify tables, the tables which used
to be part of the publication should be dropped, too. But currently, if I didn't
specify tables, the list of tables wouldn't be set to empty. Thoughts?For example:
postgres=# create publication pub for table s1.tbl;
CREATE PUBLICATION
postgres=# alter publication pub set all tables in schema s2;
ALTER PUBLICATION
postgres=# \dRp+
Publication pub
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
----------+------------+---------+---------+---------+-----------+----------
postgres | f | t | t | t | t | f
Tables:
"s1.tbl"
Tables from schemas:
"s2"
Modified
I have fixed the comments in the v28 patch attached at [1]/messages/by-id/CALDaNm0OudeDeFN7bSWPro0hgKx=1zPgcNFWnvU_G6w3mDPX0Q@mail.gmail.com.
[1]: /messages/by-id/CALDaNm0OudeDeFN7bSWPro0hgKx=1zPgcNFWnvU_G6w3mDPX0Q@mail.gmail.com
Regards,
Vignesh
On Tue, Sep 14, 2021 at 6:31 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
From Sun, Sept 12, 2021 11:13 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Sep 10, 2021 at 11:21 AM Hou Zhijie <houzj.fnst@fujitsu.com> wrote:
Attach the without-flag version and add comments about the pubobj_name.
Thanks for the changes, the suggested changes make the parsing code simpler.
I have merged the changes to the main patch. Attached v27 patch has the
changes for the same.Hi,
I have some suggestions for the documents and comments of the new syntax.
IMO, the document could be clearer in the following format.
------
Synopsis
CREATE PUBLICATION name
[ FOR ALL TABLES
| FOR publication object [, ... ] ]
[ WITH ( publication_parameter [= value] [, ... ] ) ]where publication object is one of:
TABLE [ ONLY ] table_name [ * ] [, ... ]
ALL TABLES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ]
------Attach a diff(based on v27-*) which change the doc and comments like the
following.
Thanks for the comments and the changes, I have made a few changes and
merged it into the v28 patch attached at [1]/messages/by-id/CALDaNm0OudeDeFN7bSWPro0hgKx=1zPgcNFWnvU_G6w3mDPX0Q@mail.gmail.com.
[1]: /messages/by-id/CALDaNm0OudeDeFN7bSWPro0hgKx=1zPgcNFWnvU_G6w3mDPX0Q@mail.gmail.com
Regards,
Vignesh
On Tue, Sep 14, 2021 at 2:08 PM vignesh C <vignesh21@gmail.com> wrote:
I have handled this in the patch attached.
Few comments:
=============
1.
+ * CREATE PUBLICATION FOR pub_obj [, pub_obj2] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, table2]
..
..
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, pub_obj ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name DROP pub_obj [, pub_obj ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, pub_obj ...]
In all the above places, the object names mentioned in square brackets
are not consistent. I suggest using [, ...] everywhere as that is what
we are using in docs as well.
2.
+/*
+ * Check if the relation schema is member of the schema list.
+ */
+static void
+RelSchemaIsMemberOfSchemaList(List *rels, List *schemaidlist, bool schemacheck)
Can we change the above comment as: "Check if any of the given
relation's schema is a member of the given schema list."?
3.
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication.",
+ get_namespace_name(relSchemaId)));
This and other parts of error messages in
+RelSchemaIsMemberOfSchemaList are not aligned. I think you can now
run pgindent on your patches that will solve the indentation issues in
the patch.
4.
AlterPublicationSchemas()
{
..
+ /*
+ * If the table option was not specified remove the existing tables
+ * from the publication.
+ */
+ if (!tables)
+ {
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ PublicationDropTables(pubform->oid, rels, false, true);
+ }
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
Here, you have neither locked tables to be dropped nor schemas. I
think both need to be locked as we do for tables in similar code in
AlterPublicationTables(). Can you please test via debugger what
happens if we try to drop without taking lock here and concurrently
try to drop the actual object? It should give some error. If we decide
to lock here then we should be able to pass the list of relations to
PublicationDropTables() instead of Oids which would then obviate the
need for any change to that function.
Similarly don't we need to lock schemas before dropping them in
AlterPublicationTables()?
5.
+/*
+ * Find the ObjectAddress for a publication tables in schema. The first
+ * element of the object parameter is the schema name, the second is the
+ * publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
The first part of the above comment is not clear. Can we write it as:
"Find the ObjectAddress for a publication schema. .."?
6.
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(schemaid != InvalidOid);
Isn't it better to use OidIsValid() in the above assert?
7.
@@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
@@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_PUBLICATION_REL_IN_NAMESPACE:
What is the reason for using different names for object_class and
object_type? Normally, we use the same. Is it making things clear in
any place?
8.
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *pubschemas = GetPublicationSchemas(pubid);
+
+ /* check if the relation is member of the schema list specified */
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, false);
+
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication.
+ */
+ RelSchemaIsMemberOfSchemaList(rels, pubschemas, false);
Isn't it better to concat the list of schemas and then check the
membership of relations once?
--
With Regards,
Amit Kapila.
On Tue, Sep 14, 2021 at 6:38 PM vignesh C <vignesh21@gmail.com> wrote:
I have handled this in the patch attached.
Regarding the following function in the v28-0002 patch:
+/*
+ * Check if the relation schema is member of the schema list.
+ */
+static void
+RelSchemaIsMemberOfSchemaList(List *rels, List *schemaidlist, bool schemacheck)
I think this function is not well named or commented, and I don't like
how the "schemacheck" bool parameter determines the type of objects in
the "rels" list.
I would suggest you simply split this function into two separate
functions, corresponding to each of the blocks of the "if-else" within
the for-loop of the existing RelSchemaIsMemberOfSchemaList function.
The "Is" part of the existing "RelSchemaIsMemberOfSchemaList" function
name implies a boolean return value, so seems misleading.
So I think the names of the two functions that I am suggesting should
be "CheckXXXXNotAlreadyInPublication" or something similar.
Regards,
Greg Nancarrow
Fujitsu Australia
On Tuesday, September 14, 2021 4:39 PM vignesh C <vignesh21@gmail.com> wrote:
I have handled this in the patch attached.
Thanks for updating the patch.
Here are some comments.
1)
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
...
+ /*
+ * If the table option was not specified remove the existing tables
+ * from the publication.
+ */
+ if (!tables)
+ {
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ PublicationDropTables(pubform->oid, rels, false, true);
+ }
It seems not natural to drop tables in AlterPublication*Schemas*,
I think we'd better do it in AlterPublicationTables.
2)
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
...
+ /*
+ * If ALL TABLES IN SCHEMA option was not specified remove the
+ * existing schemas from the publication.
+ */
+ List *pubschemas = GetPublicationSchemas(pubid);
+ PublicationDropSchemas(pubform->oid, pubschemas, false);
Same as 1), Is it better to modify the schema list in AlterPublicationSchemas ?
3)
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
...
/* check if the relation is member of the schema list specified */
RelSchemaIsMemberOfSchemaList(rels, schemaidlist, false);
IIRC, The check here is to check the specified tables and schemas in the
command. Personally, this seems a common operation which can be placed in
function AlterPublication(). If we move this check to AlterPublication() and if
comment 1) and 2) makes sense to you, then we don't need the new function
parameters in AlterPublicationTables() and AlterPublicationSchemas().
Best regards,
Hou zj
On Wed, Sep 15, 2021 at 12:30 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Sep 14, 2021 at 2:08 PM vignesh C <vignesh21@gmail.com> wrote:
I have handled this in the patch attached.
4. AlterPublicationSchemas() { .. + /* + * If the table option was not specified remove the existing tables + * from the publication. + */ + if (!tables) + { + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + PublicationDropTables(pubform->oid, rels, false, true); + } + + /* Identify which schemas should be dropped */ + delschemas = list_difference_oid(oldschemaids, schemaidlist); + + /* And drop them */ + PublicationDropSchemas(pubform->oid, delschemas, true);Here, you have neither locked tables to be dropped nor schemas. I
think both need to be locked as we do for tables in similar code in
AlterPublicationTables(). Can you please test via debugger what
happens if we try to drop without taking lock here and concurrently
try to drop the actual object? It should give some error. If we decide
to lock here then we should be able to pass the list of relations to
PublicationDropTables() instead of Oids which would then obviate the
need for any change to that function.Similarly don't we need to lock schemas before dropping them in
AlterPublicationTables()?
I think there is one more similar locking problem.
AlterPublicationSchemas()
{
..
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ RelSchemaIsMemberOfSchemaList(rels, schemaidlist, true);
...
...
}
Here, we don't have a lock on the relation. So, what if the relation
is concurrently dropped after you get the rel list by
GetPublicationRelations?
--
With Regards,
Amit Kapila.
On Wed, Sep 15, 2021 at 4:45 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Sep 14, 2021 at 6:38 PM vignesh C <vignesh21@gmail.com> wrote:
I have handled this in the patch attached.
Regarding the following function in the v28-0002 patch:
+/* + * Check if the relation schema is member of the schema list. + */ +static void +RelSchemaIsMemberOfSchemaList(List *rels, List *schemaidlist, bool schemacheck)I think this function is not well named or commented, and I don't like
how the "schemacheck" bool parameter determines the type of objects in
the "rels" list.
I think after fixing the comments in my previous email, the rels list
will become the same for this function but surely the extra parameter
is required for giving object-specific errors.
I would suggest you simply split this function into two separate
functions, corresponding to each of the blocks of the "if-else" within
the for-loop of the existing RelSchemaIsMemberOfSchemaList function.
The "Is" part of the existing "RelSchemaIsMemberOfSchemaList" function
name implies a boolean return value, so seems misleading.
So I think the names of the two functions that I am suggesting should
be "CheckXXXXNotAlreadyInPublication" or something similar.
I think if we write individual functions then we need to add new
functions as and when we add new object types like sequences. The
other idea could be to keep a single function like now say
CheckObjSchemaNotAlreadyInPublication and instead of the bool
parameter as the patch has now, we can keep an enum parameter
"add_obj_type" for 'rel', 'schema', 'sequence'. We can either use
exiting enum PublicationObjSpecType or define a new one for the same.
--
With Regards,
Amit Kapila.
On Thu, Sep 16, 2021 at 8:59 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Tuesday, September 14, 2021 4:39 PM vignesh C <vignesh21@gmail.com> wrote:
I have handled this in the patch attached.
Thanks for updating the patch.
Here are some comments.1) +static void +AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel, ... + /* + * If the table option was not specified remove the existing tables + * from the publication. + */ + if (!tables) + { + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + PublicationDropTables(pubform->oid, rels, false, true); + }It seems not natural to drop tables in AlterPublication*Schemas*,
I think we'd better do it in AlterPublicationTables.
I felt keeping the current way keeps it better to avoid additional
checks. Thoughts?
2) static void AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel, ... + /* + * If ALL TABLES IN SCHEMA option was not specified remove the + * existing schemas from the publication. + */ + List *pubschemas = GetPublicationSchemas(pubid); + PublicationDropSchemas(pubform->oid, pubschemas, false);Same as 1), Is it better to modify the schema list in AlterPublicationSchemas ?
This is similar to above.
3)
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
...
/* check if the relation is member of the schema list specified */
RelSchemaIsMemberOfSchemaList(rels, schemaidlist, false);IIRC, The check here is to check the specified tables and schemas in the
command. Personally, this seems a common operation which can be placed in
function AlterPublication(). If we move this check to AlterPublication() and if
comment 1) and 2) makes sense to you, then we don't need the new function
parameters in AlterPublicationTables() and AlterPublicationSchemas().
I felt we can keep the checks as is currently, else we will have to
extra checks outside and addition calls for conversion from oid to
Relation like:
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
{
if (relations)
{
if (stmt->action != DEFELEM_DROP)
{
List *rels = OpenTableList(relations);
/* check if relation is member of the schema list specified */
RelSchemaIsMemberOfSchemaList(rels, schemaidlist, false);
CloseTableList(rels);
}
AlterPublicationTables(stmt, rel, tup, relations,
list_length(schemaidlist));
}
if (schemaidlist)
AlterPublicationSchemas(stmt, rel, tup, schemaidlist,
list_length(relations));
}
Thoughts?
Regards,
Vignesh
On Wed, Sep 15, 2021 at 12:30 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Sep 14, 2021 at 2:08 PM vignesh C <vignesh21@gmail.com> wrote:
I have handled this in the patch attached.
Few comments: ============= 1. + * CREATE PUBLICATION FOR pub_obj [, pub_obj2] [WITH options] + * + * pub_obj is one of: + * + * TABLE table [, table2] .. .. - * ALTER PUBLICATION name ADD TABLE table [, table2] + * ALTER PUBLICATION name ADD pub_obj [, pub_obj ...] * - * ALTER PUBLICATION name DROP TABLE table [, table2] + * ALTER PUBLICATION name DROP pub_obj [, pub_obj ...] * - * ALTER PUBLICATION name SET TABLE table [, table2] + * ALTER PUBLICATION name SET pub_obj [, pub_obj ...]In all the above places, the object names mentioned in square brackets
are not consistent. I suggest using [, ...] everywhere as that is what
we are using in docs as well.
Modified
2. +/* + * Check if the relation schema is member of the schema list. + */ +static void +RelSchemaIsMemberOfSchemaList(List *rels, List *schemaidlist, bool schemacheck)Can we change the above comment as: "Check if any of the given
relation's schema is a member of the given schema list."?
Modified
3. + errmsg("cannot add relation \"%s.%s\" to publication", + get_namespace_name(relSchemaId), + RelationGetRelationName(rel)), + errdetail("Table's schema \"%s\" is already part of the publication.", + get_namespace_name(relSchemaId)));This and other parts of error messages in
+RelSchemaIsMemberOfSchemaList are not aligned. I think you can now
run pgindent on your patches that will solve the indentation issues in
the patch.
Changed by running pgindent.
4. AlterPublicationSchemas() { .. + /* + * If the table option was not specified remove the existing tables + * from the publication. + */ + if (!tables) + { + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + PublicationDropTables(pubform->oid, rels, false, true); + } + + /* Identify which schemas should be dropped */ + delschemas = list_difference_oid(oldschemaids, schemaidlist); + + /* And drop them */ + PublicationDropSchemas(pubform->oid, delschemas, true);Here, you have neither locked tables to be dropped nor schemas. I
think both need to be locked as we do for tables in similar code in
AlterPublicationTables(). Can you please test via debugger what
happens if we try to drop without taking lock here and concurrently
try to drop the actual object? It should give some error. If we decide
to lock here then we should be able to pass the list of relations to
PublicationDropTables() instead of Oids which would then obviate the
need for any change to that function.Similarly don't we need to lock schemas before dropping them in
AlterPublicationTables()?
we will get the following error, if concurrently dropped from another
session during debugging:
postgres=# alter publication pub1 set all tables in schema sch2;
ERROR: cache lookup failed for publication table 16418
Modified to add locking
5. +/* + * Find the ObjectAddress for a publication tables in schema. The first + * element of the object parameter is the schema name, the second is the + * publication name. + */ +static ObjectAddress +get_object_address_publication_schema(List *object, bool missing_ok)The first part of the above comment is not clear. Can we write it as:
"Find the ObjectAddress for a publication schema. .."?
Modified
6. +List * +GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt) +{ + Relation classRel; + ScanKeyData key[3]; + TableScanDesc scan; + HeapTuple tuple; + List *result = NIL; + int keycount = 0; + + Assert(schemaid != InvalidOid);Isn't it better to use OidIsValid() in the above assert?
Modified
7. @@ -974,6 +974,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_PROCEDURE: case OBJECT_PUBLICATION: case OBJECT_PUBLICATION_REL: + case OBJECT_PUBLICATION_REL_IN_NAMESPACE: case OBJECT_ROUTINE: case OBJECT_RULE: case OBJECT_SCHEMA: @@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass) case OCLASS_EXTENSION: case OCLASS_POLICY: case OCLASS_PUBLICATION: + case OCLASS_PUBLICATION_NAMESPACE: case OCLASS_PUBLICATION_REL: case OCLASS_SUBSCRIPTION: case OCLASS_TRANSFORM: @@ -2127,6 +2129,7 @@ stringify_grant_objtype(ObjectType objtype) case OBJECT_POLICY: case OBJECT_PUBLICATION: case OBJECT_PUBLICATION_REL: + case OBJECT_PUBLICATION_REL_IN_NAMESPACE: case OBJECT_ROLE: case OBJECT_RULE: case OBJECT_STATISTIC_EXT: @@ -2209,6 +2212,7 @@ stringify_adefprivs_objtype(ObjectType objtype) case OBJECT_POLICY: case OBJECT_PUBLICATION: case OBJECT_PUBLICATION_REL: + case OBJECT_PUBLICATION_REL_IN_NAMESPACE:What is the reason for using different names for object_class and
object_type? Normally, we use the same. Is it making things clear in
any place?
I thought it might be easier to review, I have changed it to the
standard way of naming object_class and object_type. Renamed it to
OBJECT_PUBLICATION_NAMESPACE.
8. + if (stmt->action == DEFELEM_ADD) + { + List *pubschemas = GetPublicationSchemas(pubid); + + /* check if the relation is member of the schema list specified */ + RelSchemaIsMemberOfSchemaList(rels, schemaidlist, false); + + /* + * Check if the relation is member of the existing schema in the + * publication. + */ + RelSchemaIsMemberOfSchemaList(rels, pubschemas, false);Isn't it better to concat the list of schemas and then check the
membership of relations once?
Modified.
Attached v29 patch has the fixes for the same.
Regards,
Vignesh
Attachments:
v29-0001-Made-the-existing-relation-cache-invalidation-an.patchtext/x-patch; charset=US-ASCII; name=v29-0001-Made-the-existing-relation-cache-invalidation-an.patchDownload
From d93834c378c256f704b9deccd80da57d004c7ea9 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 17:20:53 +0530
Subject: [PATCH v29 1/6] Made the existing relation cache invalidation and
getting the relations based on the publication partition option for a
specified relation into a function.
Made the existing relation cache invalidation code into a function. Also
made getting the relations based on the publication partition option for a
specified relation into a function. This will be used in the later
"FOR ALL TABLES IN SCHEMA" implementation patch.
---
src/backend/catalog/pg_publication.c | 67 +++++++++++++++-----------
src/backend/commands/publicationcmds.c | 42 ++++++++--------
src/include/commands/publicationcmds.h | 5 ++
3 files changed, 67 insertions(+), 47 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index d6fddd6efe..10dfe96bb2 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -238,10 +238,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,32 +307,8 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
-
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
-
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
-
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
-
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ pubrel->prrelid);
}
systable_endscan(scan);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 30929da1f5..945df49078 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -45,9 +45,6 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
@@ -329,23 +326,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
List *relids = GetPublicationRelations(pubform->oid,
PUBLICATION_PART_ALL);
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
-
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
@@ -355,6 +336,27 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
InvokeObjectPostAlterHook(PublicationRelationId, pubform->oid, 0);
}
+/*
+ * Invalidate the relations.
+ */
+void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point it's
+ * cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
/*
* Add or remove table to/from publication.
*/
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index c98d519b29..77a299bb18 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -17,6 +17,10 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
@@ -25,5 +29,6 @@ extern void RemovePublicationRelById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern void InvalidatePublicationRels(List *relids);
#endif /* PUBLICATIONCMDS_H */
--
2.30.2
v29-0002-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v29-0002-Added-schema-level-support-for-publication.patchDownload
From c1aadb62478811495e7d08452cac99c54de5a4a5 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 17:01:27 +0530
Subject: [PATCH v29 2/6] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 299 +++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 529 ++++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 20 +-
src/backend/nodes/equalfuncs.c | 15 +-
src/backend/parser/gram.y | 213 ++++---
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 10 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 36 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1237 insertions(+), 167 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..0d7de8f4c1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3903,6 +4008,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname = get_subscription_name(object->objectId,
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5712,6 +5837,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_SUBSCRIPTION:
{
char *subname;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 10dfe96bb2..ed61cbdd2d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -75,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -141,14 +167,14 @@ pg_relation_is_publishable(PG_FUNCTION_ARGS)
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -172,10 +198,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -209,7 +235,85 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
table_close(rel, RowExclusiveLock);
/* Invalidate relcache so that publication info is rebuilt. */
- CacheInvalidateRelcache(targetrel->relation);
+ CacheInvalidateRelcache(targetrel);
+
+ return myself;
+}
+
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
return myself;
}
@@ -253,7 +357,7 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -263,14 +367,14 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -317,6 +421,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -355,7 +526,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -417,6 +588,100 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication(s).
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -546,10 +811,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 945df49078..2116928681 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,12 +36,13 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -49,7 +52,12 @@ static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok,
+ bool is_oidtype);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,155 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the Relation oid list into rangevar list.
+ */
+static List *
+RelationOidsToRangevars(List *reloids)
+{
+ List *relations = NIL;
+ ListCell *lc;
+
+ foreach(lc, reloids)
+ {
+ Oid relid;
+ RangeVar *rel;
+
+ relid = lfirst_oid(lc);
+ rel = makeRangeVar(get_namespace_name(get_rel_namespace(relid)),
+ get_rel_name(relid), -1);
+ relations = lappend(relations, rel);
+ }
+
+ return relations;
+}
+
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ Node *node;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+ node = (Node *) pubobj->object;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ if (IsA(node, RangeVar))
+ *rels = lappend(*rels, (RangeVar *) node);
+ else if (IsA(node, String))
+ {
+ RangeVar *rel = makeRangeVar(NULL, strVal(node),
+ pubobj->location);
+
+ *rels = lappend(*rels, rel);
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ Oid schemaid;
+ char *schemaname;
+
+ if (!IsA(node, String))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(node);
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+ }
+ else
+ schemaid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+ Oid relSchemaId;
+
+ foreach(lc, rels)
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ Oid tableid = (Oid) lfirst_oid(lc);
+
+ relSchemaId = get_rel_namespace(tableid);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(get_rel_namespace(tableid))),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ get_rel_name(tableid),
+ get_namespace_name(get_rel_namespace(tableid))));
+ }
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ Relation rel = (Relation) lfirst(lc);
+
+ relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +309,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,17 +380,41 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- else if (stmt->for_all_tables)
+
+ if (schemaidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ Assert(list_length(schemaidlist) > 0);
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of the command.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
@@ -318,13 +501,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -362,7 +551,7 @@ InvalidatePublicationRels(List *relids)
*/
static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+ HeapTuple tup, List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
@@ -376,14 +565,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
NameStr(pubform->pubname)),
errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(tables) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(tables);
- if (stmt->tableAction == DEFELEM_ADD)
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas;
+
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
- PublicationDropTables(pubid, rels, false);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropTables(pubid, rels, false, false);
else /* DEFELEM_SET */
{
List *oldrelids = GetPublicationRelations(pubid,
@@ -391,6 +591,28 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ /* check if relation is member of the schema list specified */
+ if (schemaidlist)
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ else
+ {
+ /*
+ * If ALL TABLES IN SCHEMA option was not specified remove the
+ * existing schemas from the publication.
+ */
+ List *pubschemas = GetPublicationSchemas(pubid);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the
+ * locks will be released automatically at the end of alter
+ * publication command.
+ */
+ LockSchemaList(pubschemas);
+ PublicationDropSchemas(pubform->oid, pubschemas, false);
+ }
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +622,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,21 +633,15 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropTables(pubid, delrels, true, false);
/*
* Don't bother calculating the difference for adding, we'll catch and
@@ -440,11 +655,92 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, Relation rel,
+ HeapTuple tup, List *schemaidlist, bool relsSpecified)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+ List *rels;
+
+ /*
+ * If the table option was not specified remove the existing tables
+ * from the publication.
+ */
+ if (!relsSpecified)
+ {
+ List *relations = NIL;
+ List *tables = NIL;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ tables = RelationOidsToRangevars(rels);
+ relations = OpenTableList(tables);
+ PublicationDropTables(pubform->oid, rels, false, true);
+ CloseTableList(relations);
+ }
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -452,6 +748,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
Relation rel;
HeapTuple tup;
Form_pg_publication pubform;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
rel = table_open(PublicationRelationId, RowExclusiveLock);
@@ -471,10 +769,19 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_PUBLICATION,
stmt->pubname);
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ if (relations)
+ AlterPublicationTables(stmt, rel, tup, relations, schemaidlist);
+ if (schemaidlist)
+ AlterPublicationSchemas(stmt, rel, tup, schemaidlist,
+ list_length(relations));
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -541,9 +848,61 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to add them to a publication.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -557,16 +916,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -582,9 +940,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -617,9 +973,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -640,10 +994,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -660,8 +1013,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -669,7 +1021,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -681,11 +1033,39 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropTables(Oid pubid, List *rels, bool missing_ok, bool is_oidtype)
{
ObjectAddress obj;
ListCell *lc;
@@ -693,9 +1073,16 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
- Oid relid = RelationGetRelid(rel);
+ Oid relid;
+
+ if (is_oidtype)
+ relid = lfirst_oid(lc);
+ else
+ {
+ Relation rel = (Relation) lfirst(lc);
+
+ relid = RelationGetRelid(rel);
+ }
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
ObjectIdGetDatum(relid),
@@ -708,7 +1095,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("relation \"%s\" is not part of the publication",
- RelationGetRelationName(rel))));
+ get_rel_name(relid))));
}
ObjectAddressSet(obj, PublicationRelRelationId, prid);
@@ -716,6 +1103,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..f91b9963c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..ade93023b8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4817,7 +4817,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4830,9 +4830,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4958,12 +4958,14 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_NODE_FIELD(object);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -5887,8 +5889,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..1dcd63d64f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2302,7 +2302,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2314,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3134,9 +3134,10 @@ _equalBitString(const BitString *a, const BitString *b)
}
static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
{
- COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(object);
return true;
}
@@ -3894,8 +3895,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..47686cd777 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,6 +195,9 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
@@ -256,6 +259,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +429,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +520,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -554,6 +558,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
+%type <node> pubobj_name
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,69 +9598,117 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [[, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
+pubobj_expr:
+ pubobj_name
+ {
+ /* inheritance query, implicitly */
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
+ }
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)$1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)makeString("CURRENT_SCHEMA");
+ }
;
-publication_for_tables:
- FOR TABLE publication_table_list
+/* This can be either a schema or relation name. */
+pubobj_name:
+ ColId
{
- $$ = (Node *) $3;
+ $$ = (Node *) makeString($1);
}
- | FOR ALL TABLES
+ | ColId indirection
{
- $$ = (Node *) makeInteger(true);
+ $$ = (Node *) makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
-publication_table_list:
- publication_table
- { $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
;
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9720,28 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12485,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -12453,7 +12515,6 @@ relation_expr:
}
;
-
relation_expr_list:
relation_expr { $$ = list_make1($1); }
| relation_expr_list ',' relation_expr { $$ = lappend($1, $3); }
@@ -15104,28 +15165,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17045,6 +17085,41 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r = makeRangeVar(NULL, NULL, location);
+
+ check_qualified_name(namelist, yyscanner);
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 561266aa3e..9ceb2d5e0f 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -111,10 +111,18 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..8220c72469 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -487,7 +488,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..15aacf7165 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ Node *object; /* publication object could be:
+ * RangeVar - table object
+ * String - tablename or schemaname */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 423780652f..1f2745f2ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2045,9 +2047,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v29-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v29-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 7fa230cba724822f57d6528f00cef9d8b2524230 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v29 3/6] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 +++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 192 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 33 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 347 insertions(+), 51 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..93ed3344d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4126,6 +4134,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4213,6 +4309,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10619,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18854,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..953e1f52cf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6407,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6443,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6453,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6479,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..6262b74b9e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,27 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 1f2745f2ab..a8824880dc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2051,6 +2051,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v29-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v29-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 27c26ec103f5d2bd97824d4595c0ba8c8805fdd0 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v29 4/6] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 483 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 229 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
6 files changed, 914 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index cad1b374be..037da6c793 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,89 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+ERROR: relation "testpub_nopk" is not part of the publication
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- 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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +177,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -261,18 +344,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -304,11 +390,404 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 04b34ee299..a3860091dd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -147,9 +180,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -157,12 +192,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -183,11 +218,201 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v29-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v29-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 818169505e0374cff22281e5d1d8e3dc283aaea3 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v29 5/6] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 89 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 +++++++++++++++++--
3 files changed, 208 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2f0def9b19..c18a90a691 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..af7c946498 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,17 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables and/or all tables in schema are
+ part of the publication. The <literal>SET</literal> clause will replace
+ the list of tables and/or all tables in schema in the publication with the
+ specified one, the existing tables and all tables in schema that were
+ present in the publication will be removed. The <literal>ADD</literal>
+ clause will add one or more tables and/or all tables in schema to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables and/or all tables in schema from the publication. Note that adding
+ tables and/or all tables in schema to a publication that is already
+ subscribed to will require a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal>
+ action on the subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +71,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +118,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +171,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..0ca7e7c0e6 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of schema specified in
+ <literal>FOR ALL TABLES IN SCHEMA</literal> option is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with schema's table specified as part of
+ <literal>FOR TABLE</literal> option is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v29-0006-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v29-0006-Implemented-pg_publication_objects-view.patchDownload
From 828d8135d23ca1f1d23685447663afc8fe02db4f Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v29 6/6] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c18a90a691..38293cbdba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Thu, Sep 16, 2021 at 9:54 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 15, 2021 at 12:30 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Sep 14, 2021 at 2:08 PM vignesh C <vignesh21@gmail.com> wrote:
I have handled this in the patch attached.
4. AlterPublicationSchemas() { .. + /* + * If the table option was not specified remove the existing tables + * from the publication. + */ + if (!tables) + { + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + PublicationDropTables(pubform->oid, rels, false, true); + } + + /* Identify which schemas should be dropped */ + delschemas = list_difference_oid(oldschemaids, schemaidlist); + + /* And drop them */ + PublicationDropSchemas(pubform->oid, delschemas, true);Here, you have neither locked tables to be dropped nor schemas. I
think both need to be locked as we do for tables in similar code in
AlterPublicationTables(). Can you please test via debugger what
happens if we try to drop without taking lock here and concurrently
try to drop the actual object? It should give some error. If we decide
to lock here then we should be able to pass the list of relations to
PublicationDropTables() instead of Oids which would then obviate the
need for any change to that function.Similarly don't we need to lock schemas before dropping them in
AlterPublicationTables()?I think there is one more similar locking problem. AlterPublicationSchemas() { .. + if (stmt->action == DEFELEM_ADD) + { + List *rels; + + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + RelSchemaIsMemberOfSchemaList(rels, schemaidlist, true); ... ... }Here, we don't have a lock on the relation. So, what if the relation
is concurrently dropped after you get the rel list by
GetPublicationRelations?
This works fine without locking even after concurrent drop, I felt
this works because of MVCC.
Regards,
Vignesh
On Thu, Sep 16, 2021 at 11:24 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 15, 2021 at 4:45 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Sep 14, 2021 at 6:38 PM vignesh C <vignesh21@gmail.com> wrote:
I have handled this in the patch attached.
Regarding the following function in the v28-0002 patch:
+/* + * Check if the relation schema is member of the schema list. + */ +static void +RelSchemaIsMemberOfSchemaList(List *rels, List *schemaidlist, bool schemacheck)I think this function is not well named or commented, and I don't like
how the "schemacheck" bool parameter determines the type of objects in
the "rels" list.I think after fixing the comments in my previous email, the rels list
will become the same for this function but surely the extra parameter
is required for giving object-specific errors.I would suggest you simply split this function into two separate
functions, corresponding to each of the blocks of the "if-else" within
the for-loop of the existing RelSchemaIsMemberOfSchemaList function.
The "Is" part of the existing "RelSchemaIsMemberOfSchemaList" function
name implies a boolean return value, so seems misleading.
So I think the names of the two functions that I am suggesting should
be "CheckXXXXNotAlreadyInPublication" or something similar.I think if we write individual functions then we need to add new
functions as and when we add new object types like sequences. The
other idea could be to keep a single function like now say
CheckObjSchemaNotAlreadyInPublication and instead of the bool
parameter as the patch has now, we can keep an enum parameter
"add_obj_type" for 'rel', 'schema', 'sequence'. We can either use
exiting enum PublicationObjSpecType or define a new one for the same.
Modified the function name and changed the parameter to
PublicationObjSpecType. The changes are present at the v29 patch
posted at [1]/messages/by-id/CALDaNm1Wb=_HGd85wp2WM+fLc-8PSJ824TOZEJ6nDz3akWTidw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm1Wb=_HGd85wp2WM+fLc-8PSJ824TOZEJ6nDz3akWTidw@mail.gmail.com
Regards,
Vignesh
On Fri, Sep 17, 2021 at 5:39 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Sep 15, 2021 at 12:30 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
4. AlterPublicationSchemas() { .. + /* + * If the table option was not specified remove the existing tables + * from the publication. + */ + if (!tables) + { + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + PublicationDropTables(pubform->oid, rels, false, true); + } + + /* Identify which schemas should be dropped */ + delschemas = list_difference_oid(oldschemaids, schemaidlist); + + /* And drop them */ + PublicationDropSchemas(pubform->oid, delschemas, true);Here, you have neither locked tables to be dropped nor schemas. I
think both need to be locked as we do for tables in similar code in
AlterPublicationTables(). Can you please test via debugger what
happens if we try to drop without taking lock here and concurrently
try to drop the actual object? It should give some error. If we decide
to lock here then we should be able to pass the list of relations to
PublicationDropTables() instead of Oids which would then obviate the
need for any change to that function.Similarly don't we need to lock schemas before dropping them in
AlterPublicationTables()?we will get the following error, if concurrently dropped from another
session during debugging:
postgres=# alter publication pub1 set all tables in schema sch2;
ERROR: cache lookup failed for publication table 16418
Modified to add locking
But you haven't followed my other suggestion related to
PublicationDropTables(). I don't think after doing this, you need to
pass 'true' as the last parameter to PublicationDropTables. In fact,
you can remove that parameter altogether or in other words, we don't
need any change in PublicationDropTables for this patch. Is there a
reason why we shouldn't make this change?
Few other comments:
===================
1. The ordering of lock acquisition for schema and relation in
AlterPublicationSchemas() and AlterPublicationTables() is opposite
which would generally lead to deadlock but it won't here because we
acquire share lock on the schema. But, I think it may still be better
to keep the locking order the same and it might help us to keep schema
and relation code separate
2. One more thing, I think one can concurrently add-relation for a
particular schema and that particular schema. To protect that
AlterPublication should acquire an exclusive lock similar to how we do
in AlterSubscription.
3.
+ /*
+ * If the table option was not specified remove the existing tables
+ * from the publication.
+ */
+ if (!relsSpecified)
+ {
+ List *relations = NIL;
+ List *tables = NIL;
+
+ rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ tables = RelationOidsToRangevars(rels);
+ relations = OpenTableList(tables);
One problem with using OpenTableList here is that it might try to lock
inherited children twice. Also, you don't need to first convert to
rangevar for locking relations, you can directly use table_open here.
4.
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)$1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)makeString("CURRENT_SCHEMA");
+ }
;
-publication_for_tables:
- FOR TABLE publication_table_list
+/* This can be either a schema or relation name. */
+pubobj_name:
+ ColId
{
- $$ = (Node *) $3;
+ $$ = (Node *) makeString($1);
}
- | FOR ALL TABLES
+ | ColId indirection
{
- $$ = (Node *) makeInteger(true);
+ $$ = (Node *) makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
In some places, you have given space after (Node *) and at other
places, there is no space. Isn't it better to be consistent?
5.
+/* This can be either a schema or relation name. */
+pubobj_name:
Here, we can modify the comment as "This can be either a schema or
relation name. For relations, the inheritance will be implicit." And
then remove the inheritance related comment from code below:
+ /* inheritance query, implicitly */
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
--
With Regards,
Amit Kapila.
On Fri, Sep 17, 2021 at 5:40 PM vignesh C <vignesh21@gmail.com> wrote:
On Thu, Sep 16, 2021 at 9:54 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
I think there is one more similar locking problem. AlterPublicationSchemas() { .. + if (stmt->action == DEFELEM_ADD) + { + List *rels; + + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + RelSchemaIsMemberOfSchemaList(rels, schemaidlist, true); ... ... }Here, we don't have a lock on the relation. So, what if the relation
is concurrently dropped after you get the rel list by
GetPublicationRelations?This works fine without locking even after concurrent drop, I felt
this works because of MVCC.
Can you share the exact scenario you have tested? I think here it can
give a wrong error because it might access invalid cache entry, so I
think a lock is required here. Also, as said before, this might help
to make the rel list consistent in function
CheckObjSchemaNotAlreadyInPublication().
--
With Regards,
Amit Kapila.
On Mon, Sep 20, 2021 at 3:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Sep 17, 2021 at 5:40 PM vignesh C <vignesh21@gmail.com> wrote:
On Thu, Sep 16, 2021 at 9:54 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
I think there is one more similar locking problem. AlterPublicationSchemas() { .. + if (stmt->action == DEFELEM_ADD) + { + List *rels; + + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + RelSchemaIsMemberOfSchemaList(rels, schemaidlist, true); ... ... }Here, we don't have a lock on the relation. So, what if the relation
is concurrently dropped after you get the rel list by
GetPublicationRelations?This works fine without locking even after concurrent drop, I felt
this works because of MVCC.Can you share the exact scenario you have tested? I think here it can
give a wrong error because it might access invalid cache entry, so I
think a lock is required here. Also, as said before, this might help
to make the rel list consistent in function
CheckObjSchemaNotAlreadyInPublication().
This is the scenario I tried:
create schema sch1;
create table sch1.t1 (c1 int);
create publication pub1 for table sch1.t1;
alter publication pub1 add all tables in schema sch1; -- concurrently
drop table sch1.t1 from another session.
I will add the locking and changing of
CheckObjSchemaNotAlreadyInPublication in the next version.
Regards,
Vignesh
On Fri, Sep 17, 2021 at 10:09 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v29 patch has the fixes for the same.
Some minor comments on the v29-0002 patch:
(1)
In get_object_address_publication_schema(), the error message:
+ errmsg("publication tables of schema \"%s\" in publication \"%s\"
does not exist",
isn't grammatically correct. It should probably be:
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" do
not exist",
(2)
getPublicationSchemaInfo() function header.
"string" should be "strings"
BEFORE:
+ * nspname. Both pubname and nspname are palloc'd string which will be freed by
AFTER:
+ * nspname. Both pubname and nspname are palloc'd strings which will
be freed by
(3)
getPublicationSchemaInfo()
In the case of "if (!(*nspname))", the following line should probably
be added before returning false:
*pubname = NULL;
(4)
GetAllSchemasPublicationRelations() function header
Shouldn't:
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication(s).
actually say:
+ * Gets the list of all relations published by a FOR ALL TABLES IN SCHEMA
+ * publication.
since it is for a specified publication?
(5)
I'm wondering, in CheckObjSchemaNotAlreadyInPublication(), instead of
checking "checkobjtype" each loop iteration, wouldn't it be better to
just use the same for-loop in each IF block?
Regards,
Greg Nancarrow
Fujitsu Australia
On Tue, Sep 21, 2021 at 9:03 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Sep 17, 2021 at 10:09 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v29 patch has the fixes for the same.
Some minor comments on the v29-0002 patch:
(1)
In get_object_address_publication_schema(), the error message:+ errmsg("publication tables of schema \"%s\" in publication \"%s\"
does not exist",isn't grammatically correct. It should probably be:
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" do
not exist",
"does not exist" is used across the file. Should we keep it like that
to maintain consistency. Thoughts?
(2)
getPublicationSchemaInfo() function header.
"string" should be "strings"BEFORE: + * nspname. Both pubname and nspname are palloc'd string which will be freed by AFTER: + * nspname. Both pubname and nspname are palloc'd strings which will be freed by
I will change it in the next version.
(3)
getPublicationSchemaInfo()In the case of "if (!(*nspname))", the following line should probably
be added before returning false:*pubname = NULL;
In case of failure we return false and don't access it. I felt we
could keep it as it is. Thoughts?
(4)
GetAllSchemasPublicationRelations() function headerShouldn't:
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA + * publication(s).actually say:
+ * Gets the list of all relations published by a FOR ALL TABLES IN SCHEMA + * publication.since it is for a specified publication?
I will change it in the next version.
(5)
I'm wondering, in CheckObjSchemaNotAlreadyInPublication(), instead of
checking "checkobjtype" each loop iteration, wouldn't it be better to
just use the same for-loop in each IF block?
I will be changing it to:
static void
CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
PublicationObjSpecType checkobjtype)
{
ListCell *lc;
foreach(lc, rels)
{
Relation rel = (Relation) lfirst(lc);
Oid relSchemaId = RelationGetNamespace(rel);
if (list_member_oid(schemaidlist, relSchemaId))
{
if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add schema \"%s\" to publication",
get_namespace_name(relSchemaId)),
errdetail("Table \"%s\" in schema \"%s\" is already part
of the publication, adding the same schema is not supported.",
RelationGetRelationName(rel),
get_namespace_name(relSchemaId)));
else if (checkobjtype == PUBLICATIONOBJ_TABLE)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add relation \"%s.%s\" to publication",
get_namespace_name(relSchemaId),
RelationGetRelationName(rel)),
errdetail("Table's schema \"%s\" is already part of the
publication.",
get_namespace_name(relSchemaId)));
}
}
}
After the change checkobjtype will be checked only once in case of error.
Regards,
Vignesh
On Tue, Sep 21, 2021 at 4:12 PM vignesh C <vignesh21@gmail.com> wrote:
(1)
In get_object_address_publication_schema(), the error message:+ errmsg("publication tables of schema \"%s\" in publication \"%s\"
does not exist",isn't grammatically correct. It should probably be:
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" do
not exist","does not exist" is used across the file. Should we keep it like that
to maintain consistency. Thoughts?
When it's singular, "does not exist" is correct.
I think currently only this case exists in the publication code.
e.g.
"publication \"%s\" does not exist"
"publication relation \"%s\" in publication \"%s\" does not exist"
But "publication tables" is plural, so it needs to say "do not exist"
rather than "does not exist".
In the case of "if (!(*nspname))", the following line should probably
be added before returning false:*pubname = NULL;
In case of failure we return false and don't access it. I felt we
could keep it as it is. Thoughts?
OK then, I might be being a bit pedantic.
(I was just thinking, strictly speaking, we probably shouldn't be
writing into the caller's pass-by-reference parameters in the case
false is returned)
(5)
I'm wondering, in CheckObjSchemaNotAlreadyInPublication(), instead of
checking "checkobjtype" each loop iteration, wouldn't it be better to
just use the same for-loop in each IF block?I will be changing it to:
static void
CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
PublicationObjSpecType checkobjtype)
{
ListCell *lc;foreach(lc, rels)
{
Relation rel = (Relation) lfirst(lc);
Oid relSchemaId = RelationGetNamespace(rel);if (list_member_oid(schemaidlist, relSchemaId))
{
if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add schema \"%s\" to publication",
get_namespace_name(relSchemaId)),
errdetail("Table \"%s\" in schema \"%s\" is already part
of the publication, adding the same schema is not supported.",
RelationGetRelationName(rel),
get_namespace_name(relSchemaId)));
else if (checkobjtype == PUBLICATIONOBJ_TABLE)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add relation \"%s.%s\" to publication",
get_namespace_name(relSchemaId),
RelationGetRelationName(rel)),
errdetail("Table's schema \"%s\" is already part of the
publication.",
get_namespace_name(relSchemaId)));
}
}
}
After the change checkobjtype will be checked only once in case of error.
OK.
One thing related to this code is the following:
i)
postgres=# create publication pub1 for all tables in schema sch1,
table sch1.test;
ERROR: cannot add relation "sch1.test" to publication
DETAIL: Table's schema "sch1" is already part of the publication.
ii)
postgres=# create publication pub1 for table sch1.test, all tables in
schema sch1;
ERROR: cannot add relation "sch1.test" to publication
DETAIL: Table's schema "sch1" is already part of the publication.
Notice that in case (ii), the same error message is used, but the
order of items to be "added" to the publication is the reverse of case
(i), and really implies the table "sch1.test" was added first, but
this is not reflected by the error message. So it seems slightly odd
to say the schema is already part of the publication, when the table
was actually listed first.
I'm wondering if this can be improved?
One idea I had was the following more generic type of message, but I'm
not 100% happy with the wording:
DETAIL: Schema "sch1" and one of its tables can't separately be
part of the publication.
Regards,
Greg Nancarrow
Fujitsu Australia
On Mon, Sep 20, 2021 at 3:56 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Sep 17, 2021 at 5:39 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Sep 15, 2021 at 12:30 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
4. AlterPublicationSchemas() { .. + /* + * If the table option was not specified remove the existing tables + * from the publication. + */ + if (!tables) + { + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + PublicationDropTables(pubform->oid, rels, false, true); + } + + /* Identify which schemas should be dropped */ + delschemas = list_difference_oid(oldschemaids, schemaidlist); + + /* And drop them */ + PublicationDropSchemas(pubform->oid, delschemas, true);Here, you have neither locked tables to be dropped nor schemas. I
think both need to be locked as we do for tables in similar code in
AlterPublicationTables(). Can you please test via debugger what
happens if we try to drop without taking lock here and concurrently
try to drop the actual object? It should give some error. If we decide
to lock here then we should be able to pass the list of relations to
PublicationDropTables() instead of Oids which would then obviate the
need for any change to that function.Similarly don't we need to lock schemas before dropping them in
AlterPublicationTables()?we will get the following error, if concurrently dropped from another
session during debugging:
postgres=# alter publication pub1 set all tables in schema sch2;
ERROR: cache lookup failed for publication table 16418
Modified to add lockingBut you haven't followed my other suggestion related to
PublicationDropTables(). I don't think after doing this, you need to
pass 'true' as the last parameter to PublicationDropTables. In fact,
you can remove that parameter altogether or in other words, we don't
need any change in PublicationDropTables for this patch. Is there a
reason why we shouldn't make this change?
Modified.
Few other comments:
===================
1. The ordering of lock acquisition for schema and relation in
AlterPublicationSchemas() and AlterPublicationTables() is opposite
which would generally lead to deadlock but it won't here because we
acquire share lock on the schema. But, I think it may still be better
to keep the locking order the same and it might help us to keep schema
and relation code separate
Modified
2. One more thing, I think one can concurrently add-relation for a
particular schema and that particular schema. To protect that
AlterPublication should acquire an exclusive lock similar to how we do
in AlterSubscription.
Modified
3. + /* + * If the table option was not specified remove the existing tables + * from the publication. + */ + if (!relsSpecified) + { + List *relations = NIL; + List *tables = NIL; + + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + tables = RelationOidsToRangevars(rels); + relations = OpenTableList(tables);One problem with using OpenTableList here is that it might try to lock
inherited children twice. Also, you don't need to first convert to
rangevar for locking relations, you can directly use table_open here.
Modified
4. + | extended_relation_expr + { + $$ = makeNode(PublicationObjSpec); + $$->object = (Node *)$1; + } + | CURRENT_SCHEMA + { + $$ = makeNode(PublicationObjSpec); + $$->object = (Node *)makeString("CURRENT_SCHEMA"); + } ;-publication_for_tables: - FOR TABLE publication_table_list +/* This can be either a schema or relation name. */ +pubobj_name: + ColId { - $$ = (Node *) $3; + $$ = (Node *) makeString($1); } - | FOR ALL TABLES + | ColId indirection { - $$ = (Node *) makeInteger(true); + $$ = (Node *) makeRangeVarFromQualifiedName($1, $2, @1, yyscanner); }In some places, you have given space after (Node *) and at other
places, there is no space. Isn't it better to be consistent?
I have changed it to "(Node *)" without space in *.y, I did not change
in *.c as I noticed it is used with space like "(Node *) " in other
places of *.c files.
5. +/* This can be either a schema or relation name. */ +pubobj_name:Here, we can modify the comment as "This can be either a schema or
relation name. For relations, the inheritance will be implicit."
Modified
And
then remove the inheritance related comment from code below:
+ /* inheritance query, implicitly */ + $$ = makeNode(PublicationObjSpec); + $$->object = $1;
Modified.
Attached v30 patch has the fixes for the same.
Regards,
Vignesh
Attachments:
v30-0001-Made-the-existing-relation-cache-invalidation-an.patchtext/x-patch; charset=US-ASCII; name=v30-0001-Made-the-existing-relation-cache-invalidation-an.patchDownload
From 4e079138833e82af806d1d6d48c2b7529169612e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 30 Aug 2021 17:20:53 +0530
Subject: [PATCH v30 1/6] Made the existing relation cache invalidation and
getting the relations based on the publication partition option for a
specified relation into a function.
Made the existing relation cache invalidation code into a function. Also
made getting the relations based on the publication partition option for a
specified relation into a function. This will be used in the later
"FOR ALL TABLES IN SCHEMA" implementation patch.
---
src/backend/catalog/pg_publication.c | 67 +++++++++++++++-----------
src/backend/commands/publicationcmds.c | 42 ++++++++--------
src/include/commands/publicationcmds.h | 5 ++
3 files changed, 67 insertions(+), 47 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index d6fddd6efe..10dfe96bb2 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -238,10 +238,47 @@ GetRelationPublications(Oid relid)
return result;
}
+/*
+ * Gets the relations based on the publication partition option for a specified
+ * relation.
+ */
+static List *
+GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
+ Oid relid)
+{
+ if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
+ pub_partopt != PUBLICATION_PART_ROOT)
+ {
+ List *all_parts = find_all_inheritors(relid, NoLock,
+ NULL);
+
+ if (pub_partopt == PUBLICATION_PART_ALL)
+ result = list_concat(result, all_parts);
+ else if (pub_partopt == PUBLICATION_PART_LEAF)
+ {
+ ListCell *lc;
+
+ foreach(lc, all_parts)
+ {
+ Oid partOid = lfirst_oid(lc);
+
+ if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
+ result = lappend_oid(result, partOid);
+ }
+ }
+ else
+ Assert(false);
+ }
+ else
+ result = lappend_oid(result, relid);
+
+ return result;
+}
+
/*
* Gets list of relation oids for a publication.
*
- * This should only be used for normal publications, the FOR ALL TABLES
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
*/
List *
@@ -270,32 +307,8 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Form_pg_publication_rel pubrel;
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
-
- if (get_rel_relkind(pubrel->prrelid) == RELKIND_PARTITIONED_TABLE &&
- pub_partopt != PUBLICATION_PART_ROOT)
- {
- List *all_parts = find_all_inheritors(pubrel->prrelid, NoLock,
- NULL);
-
- if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
- else if (pub_partopt == PUBLICATION_PART_LEAF)
- {
- ListCell *lc;
-
- foreach(lc, all_parts)
- {
- Oid partOid = lfirst_oid(lc);
-
- if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
- }
- }
- else
- Assert(false);
- }
- else
- result = lappend_oid(result, pubrel->prrelid);
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ pubrel->prrelid);
}
systable_endscan(scan);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 30929da1f5..945df49078 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -45,9 +45,6 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
-/* Same as MAXNUMMESSAGES in sinvaladt.c */
-#define MAX_RELCACHE_INVAL_MSGS 4096
-
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
@@ -329,23 +326,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
List *relids = GetPublicationRelations(pubform->oid,
PUBLICATION_PART_ALL);
- /*
- * We don't want to send too many individual messages, at some point
- * it's cheaper to just reset whole relcache.
- */
- if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
- {
- ListCell *lc;
-
- foreach(lc, relids)
- {
- Oid relid = lfirst_oid(lc);
-
- CacheInvalidateRelcacheByRelid(relid);
- }
- }
- else
- CacheInvalidateRelcacheAll();
+ InvalidatePublicationRels(relids);
}
ObjectAddressSet(obj, PublicationRelationId, pubform->oid);
@@ -355,6 +336,27 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
InvokeObjectPostAlterHook(PublicationRelationId, pubform->oid, 0);
}
+/*
+ * Invalidate the relations.
+ */
+void
+InvalidatePublicationRels(List *relids)
+{
+ /*
+ * We don't want to send too many individual messages, at some point it's
+ * cheaper to just reset whole relcache.
+ */
+ if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)
+ {
+ ListCell *lc;
+
+ foreach(lc, relids)
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+ }
+ else
+ CacheInvalidateRelcacheAll();
+}
+
/*
* Add or remove table to/from publication.
*/
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index c98d519b29..77a299bb18 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -17,6 +17,10 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "utils/inval.h"
+
+/* Same as MAXNUMMESSAGES in sinvaladt.c */
+#define MAX_RELCACHE_INVAL_MSGS 4096
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
@@ -25,5 +29,6 @@ extern void RemovePublicationRelById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern void InvalidatePublicationRels(List *relids);
#endif /* PUBLICATIONCMDS_H */
--
2.30.2
v30-0002-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v30-0002-Added-schema-level-support-for-publication.patchDownload
From 48385b38bb5977c3086b460aff1c74caaaa38bdb Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 17:01:27 +0530
Subject: [PATCH v30 2/6] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 299 ++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 598 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 20 +-
src/backend/nodes/equalfuncs.c | 15 +-
src/backend/parser/gram.y | 215 +++++--
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 15 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 36 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1278 insertions(+), 202 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..ed442c33c5 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 10dfe96bb2..e1d265889d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,16 +28,18 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
+#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -75,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -141,14 +167,14 @@ pg_relation_is_publishable(PG_FUNCTION_ARGS)
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -172,10 +198,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -209,7 +235,85 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
table_close(rel, RowExclusiveLock);
/* Invalidate relcache so that publication info is rebuilt. */
- CacheInvalidateRelcache(targetrel->relation);
+ CacheInvalidateRelcache(targetrel);
+
+ return myself;
+}
+
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
return myself;
}
@@ -253,7 +357,7 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -263,14 +367,14 @@ GetPublicationPartOptRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -317,6 +421,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -355,7 +526,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -417,6 +588,100 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPublicationPartOptRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -546,10 +811,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 945df49078..68be61ae79 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,123 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ Node *node;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+ node = (Node *) pubobj->object;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ if (IsA(node, RangeVar))
+ *rels = lappend(*rels, (RangeVar *) node);
+ else if (IsA(node, String))
+ {
+ RangeVar *rel = makeRangeVar(NULL, strVal(node),
+ pubobj->location);
+
+ *rels = lappend(*rels, rel);
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ Oid schemaid;
+ char *schemaname;
+
+ if (!IsA(node, String))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(node);
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+ }
+ else
+ schemaid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +277,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,17 +348,36 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- else if (stmt->for_all_tables)
+
+ if (schemaidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ Assert(list_length(schemaidlist) > 0);
+
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
@@ -318,13 +464,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,90 +513,171 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, List *rels,
+ List *delrels)
{
- List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ Assert(list_length(rels) > 0);
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
-
- if (stmt->tableAction == DEFELEM_ADD)
+ if (stmt->action == DEFELEM_ADD)
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
- List *oldrelids = GetPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- List *delrels = NIL;
- ListCell *oldlc;
+ PublicationDropTables(pubid, delrels, true);
- /* Calculate which relations to drop. */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- bool found = false;
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddTables(pubid, rels, true, stmt);
+ }
+}
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
- {
- found = true;
- break;
- }
- }
- /* Not yet in the list, open it and add to the list */
- if (!found)
- {
- Relation oldrel;
- PublicationRelInfo *pubrel;
+/*
+ * Get the relations that should be deleted for the publication.
+ */
+static void
+GetAlterPublicationDelRelations(DefElemAction action, Oid pubid, List *rels,
+ List **delrels)
+{
+ List *oldrelids;
+ ListCell *oldlc;
+
+ if (action != DEFELEM_SET)
+ return;
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
+ oldrelids = GetPublicationRelations(pubid, PUBLICATION_PART_ROOT);
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ /* Calculate which relations to drop. */
+ foreach(oldlc, oldrelids)
+ {
+ Oid oldrelid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, rels)
+ {
+ Relation newrel = (Relation) lfirst(newlc);
- delrels = lappend(delrels, pubrel);
+ if (RelationGetRelid(newrel) == oldrelid)
+ {
+ found = true;
+ break;
}
}
+ /* Not yet in the list, open it and add to the list */
+ if (!found)
+ *delrels = lappend_oid(*delrels, oldrelid);
+ }
+}
- /* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *schemaidlist, List *delschemas)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if (stmt->action == DEFELEM_ADD)
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+CheckPublicationAlterTables(DefElemAction action, HeapTuple tup,
+ List *rels, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (action == DEFELEM_ADD)
+ {
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ List *schemas = list_concat(schemaidlist,
+ GetPublicationSchemas(pubform->oid));
- CloseTableList(delrels);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
+ }
+ else if (action == DEFELEM_SET && list_length(schemaidlist))
+ {
+ /* check if relation is member of the schema list specified */
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
}
+}
- CloseTableList(rels);
+/*
+ * Check if schemas can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+CheckPublicationAlterSchemas(DefElemAction action, HeapTuple tup,
+ List *schemaidlist, List *relations)
+{
+ Form_pg_publication pubform;
+
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications."));
+
+ if ((action == DEFELEM_ADD || action == DEFELEM_SET) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ if (action == DEFELEM_ADD)
+ CheckObjSchemaNotAlreadyInPublication(relations, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
}
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +707,75 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ /* Lock the publication so nobody else can do anything with it. */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ if (list_length(relations))
+ {
+ List *rels;
+ List *delschemas = NIL;
+ List *delrelids = NIL;
+ List *delrels = NIL;
+
+ if (stmt->action == DEFELEM_SET && !list_length(schemaidlist))
+ {
+ delschemas = GetPublicationSchemas(pubform->oid);
+ LockSchemaList(delschemas);
+ }
+
+ rels = OpenTableList(relations);
+ GetAlterPublicationDelRelations(stmt->action, pubform->oid, rels,
+ &delrelids);
+
+ CheckPublicationAlterTables(stmt->action, tup, rels, schemaidlist);
+ delrels = OpenReliIdList(delrelids);
+
+ /* remove the existing schemas from the publication */
+ PublicationDropSchemas(pubform->oid, delschemas, false);
+
+ AlterPublicationTables(stmt, tup, rels, delrels);
+ CloseTableList(delrels);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist))
+ {
+ List *pubrelids = NIL;
+ List *pubrels = NIL;
+ List *delschemas = NIL;
+
+ if (stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET)
+ pubrelids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ROOT);
+
+ if (stmt->action == DEFELEM_SET)
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+ LockSchemaList(delschemas);
+ }
+
+ LockSchemaList(schemaidlist);
+ pubrels = OpenReliIdList(pubrelids);
+ CheckPublicationAlterSchemas(stmt->action, tup, schemaidlist,
+ pubrels);
+
+ if (stmt->action == DEFELEM_SET && !list_length(relations))
+ PublicationDropTables(pubform->oid, pubrels, false);
+
+ CloseTableList(pubrels);
+ AlterPublicationSchemas(stmt, tup, schemaidlist, delschemas);
+ }
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -527,7 +828,7 @@ RemovePublicationById(Oid pubid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication %u", pubid);
- pubform = (Form_pg_publication)GETSTRUCT(tup);
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate relcache so that publication info is rebuilt. */
if (pubform->puballtables)
@@ -541,9 +842,84 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to prevent concurrent schema deletion. No need to unlock the
+ * schemas, the locks will be released at the end of the command.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -557,16 +933,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -582,9 +957,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -617,9 +990,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -640,10 +1011,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -660,8 +1030,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -669,7 +1038,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -681,6 +1050,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -693,8 +1090,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -716,6 +1112,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..f91b9963c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..ade93023b8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4817,7 +4817,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4830,9 +4830,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4958,12 +4958,14 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_NODE_FIELD(object);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -5887,8 +5889,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..1dcd63d64f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2302,7 +2302,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2314,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3134,9 +3134,10 @@ _equalBitString(const BitString *a, const BitString *b)
}
static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
{
- COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(object);
return true;
}
@@ -3894,8 +3895,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..277c6c513e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,6 +195,9 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
@@ -256,6 +259,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +429,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +520,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -554,6 +558,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
+%type <node> pubobj_name
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,69 +9598,119 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [[, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
+pubobj_expr:
+ pubobj_name
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
+ }
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)$1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)makeString("CURRENT_SCHEMA");
+ }
;
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * This can be either a schema or relation name. For relations, the inheritance
+ * will be implicit.
+ */
+pubobj_name:
+ ColId
{
- $$ = (Node *) $3;
+ $$ = (Node *)makeString($1);
}
- | FOR ALL TABLES
+ | ColId indirection
{
- $$ = (Node *) makeInteger(true);
+ $$ = (Node *)makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
-publication_table_list:
- publication_table
- { $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
;
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9722,28 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12487,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -12453,7 +12517,6 @@ relation_expr:
}
;
-
relation_expr_list:
relation_expr { $$ = list_make1($1); }
| relation_expr_list ',' relation_expr { $$ = lappend($1, $3); }
@@ -15104,28 +15167,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17045,6 +17087,41 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r = makeRangeVar(NULL, NULL, location);
+
+ check_qualified_name(namelist, yyscanner);
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 561266aa3e..334fcad694 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,10 +106,18 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..8220c72469 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -487,7 +488,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..15aacf7165 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ Node *object; /* publication object could be:
+ * RangeVar - table object
+ * String - tablename or schemaname */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 402a6617a9..bfc6909a43 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v30-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v30-0003-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 4b2d98d818bbb0e4138ff27489c6eb710a8f9d1f Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v30 3/6] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 +++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 192 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 33 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 347 insertions(+), 51 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..93ed3344d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4126,6 +4134,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4213,6 +4309,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10619,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18854,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..953e1f52cf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6407,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6443,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6453,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6479,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..6262b74b9e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,27 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bfc6909a43..80510a9e1a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v30-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v30-0004-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 7967cb5ef0373911e70c4332ee59e4041e8fadaf Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v30 4/6] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 483 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 229 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
6 files changed, 914 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index cad1b374be..037da6c793 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,89 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+ERROR: relation "testpub_nopk" is not part of the publication
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- 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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +177,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -261,18 +344,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -304,11 +390,404 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 04b34ee299..a3860091dd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -147,9 +180,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -157,12 +192,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -183,11 +218,201 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v30-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v30-0005-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 592f526b788f2c11c0f7b2fe64a3d660d393ab5a Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v30 5/6] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 89 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 +++++++++++++++++--
3 files changed, 208 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2f0def9b19..c18a90a691 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..af7c946498 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,17 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables and/or all tables in schema are
+ part of the publication. The <literal>SET</literal> clause will replace
+ the list of tables and/or all tables in schema in the publication with the
+ specified one, the existing tables and all tables in schema that were
+ present in the publication will be removed. The <literal>ADD</literal>
+ clause will add one or more tables and/or all tables in schema to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables and/or all tables in schema from the publication. Note that adding
+ tables and/or all tables in schema to a publication that is already
+ subscribed to will require a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal>
+ action on the subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +71,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +118,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +171,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..0ca7e7c0e6 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of schema specified in
+ <literal>FOR ALL TABLES IN SCHEMA</literal> option is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with schema's table specified as part of
+ <literal>FOR TABLE</literal> option is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v30-0006-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v30-0006-Implemented-pg_publication_objects-view.patchDownload
From c229c3586bbc428a66156536584f13a45b7ff4fb Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v30 6/6] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c18a90a691..38293cbdba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Mon, Sep 20, 2021 at 4:20 PM vignesh C <vignesh21@gmail.com> wrote:
On Mon, Sep 20, 2021 at 3:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Sep 17, 2021 at 5:40 PM vignesh C <vignesh21@gmail.com> wrote:
On Thu, Sep 16, 2021 at 9:54 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
I think there is one more similar locking problem. AlterPublicationSchemas() { .. + if (stmt->action == DEFELEM_ADD) + { + List *rels; + + rels = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT); + RelSchemaIsMemberOfSchemaList(rels, schemaidlist, true); ... ... }Here, we don't have a lock on the relation. So, what if the relation
is concurrently dropped after you get the rel list by
GetPublicationRelations?This works fine without locking even after concurrent drop, I felt
this works because of MVCC.Can you share the exact scenario you have tested? I think here it can
give a wrong error because it might access invalid cache entry, so I
think a lock is required here. Also, as said before, this might help
to make the rel list consistent in function
CheckObjSchemaNotAlreadyInPublication().This is the scenario I tried:
create schema sch1;
create table sch1.t1 (c1 int);
create publication pub1 for table sch1.t1;
alter publication pub1 add all tables in schema sch1; -- concurrently
drop table sch1.t1 from another session.I will add the locking and changing of
CheckObjSchemaNotAlreadyInPublication in the next version.
I have made the changes for the above at the v30 patch attached at [1]/messages/by-id/CALDaNm3aFtXpkD4m28-ENG9F4faBEVdGNUrEhgKV0pHr2S_C2g@mail.gmail.com.
[1]: /messages/by-id/CALDaNm3aFtXpkD4m28-ENG9F4faBEVdGNUrEhgKV0pHr2S_C2g@mail.gmail.com
Regards,
Vignesh
On Tue, Sep 21, 2021 at 9:03 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Sep 17, 2021 at 10:09 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v29 patch has the fixes for the same.
Some minor comments on the v29-0002 patch:
(1)
In get_object_address_publication_schema(), the error message:+ errmsg("publication tables of schema \"%s\" in publication \"%s\"
does not exist",isn't grammatically correct. It should probably be:
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" do
not exist",
Modified
(2)
getPublicationSchemaInfo() function header.
"string" should be "strings"BEFORE: + * nspname. Both pubname and nspname are palloc'd string which will be freed by AFTER: + * nspname. Both pubname and nspname are palloc'd strings which will be freed by
Modified
(3)
getPublicationSchemaInfo()In the case of "if (!(*nspname))", the following line should probably
be added before returning false:*pubname = NULL;
I left it as it is, as we don't access it in case of return false.
(4)
GetAllSchemasPublicationRelations() function headerShouldn't:
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA + * publication(s).actually say:
+ * Gets the list of all relations published by a FOR ALL TABLES IN SCHEMA + * publication.since it is for a specified publication?
Modified
(5)
I'm wondering, in CheckObjSchemaNotAlreadyInPublication(), instead of
checking "checkobjtype" each loop iteration, wouldn't it be better to
just use the same for-loop in each IF block?
Modified
I have made the changes for the above at the v30 patch attached at [1]/messages/by-id/CALDaNm3aFtXpkD4m28-ENG9F4faBEVdGNUrEhgKV0pHr2S_C2g@mail.gmail.com.
[1]: /messages/by-id/CALDaNm3aFtXpkD4m28-ENG9F4faBEVdGNUrEhgKV0pHr2S_C2g@mail.gmail.com
Regards,
Vignesh
On Wed, Sep 22, 2021 at 4:02 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v30 patch has the fixes for the same.
Thanks for all the patch updates.
I have some suggested updates to the v30-0005 documentation patch:
doc/src/sgml/ref/alter_publication.sgml
(1)
I'm thinking it might be better to simplify the description, because
it's a bit wordy and difficult to read with the "all tables in schema"
bits.
Suggested update is below (thoughts?):
BEFORE:
+ The first three variants change which tables and/or all tables in schema are
+ part of the publication. The <literal>SET</literal> clause will replace
+ the list of tables and/or all tables in schema in the publication with the
+ specified one, the existing tables and all tables in schema that were
+ present in the publication will be removed. The <literal>ADD</literal>
+ clause will add one or more tables and/or all tables in schema to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables and/or all tables in schema from the publication. Note that adding
+ tables and/or all tables in schema to a publication that is already
+ subscribed to will require a <literal>ALTER SUBSCRIPTION ...
REFRESH PUBLICATION</literal>
+ action on the subscribing side in order to become effective.
AFTER:
+ The first three variants change which tables/schemas are
+ part of the publication. The <literal>SET</literal> clause will replace
+ the list of tables/schemas in the publication with the
+ specified list; the existing tables/schemas that were
+ present in the publication will be removed. The <literal>ADD</literal>
+ clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding
+ tables/schemas to a publication that is already
+ subscribed to will require a <literal>ALTER SUBSCRIPTION ...
REFRESH PUBLICATION</literal>
+ action on the subscribing side in order to become effective.
doc/src/sgml/ref/create_publication.sgml
(2)
I suggest an update similar to the following:
BEFORE:
+ Specifying a table that is part of schema specified in
+ <literal>FOR ALL TABLES IN SCHEMA</literal> option is not supported.
AFTER:
+ Specifying a table that is part of a schema already included in
the publication is not supported.
(3)
I find the following description a little unclear:
+ <para>
+ Specifying a schema along with schema's table specified as part of
+ <literal>FOR TABLE</literal> option is not supported.
+ </para>
Perhaps the following would be better:
+ <para>
+ Specifying a schema that contains a table already included in the
+ publication is not supported.
+ </para>
(4)
Minor fix:
BEFORE:
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
AFTER:
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
Regards,
Greg Nancarrow
Fujitsu Australia
On Wednesday, September 22, 2021 2:02 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v30 patch has the fixes for the same.
Thanks for updating the patches.
I have one comment.
@@ -474,7 +707,75 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
...
+ if (list_length(relations))
+ {
...
+ /* remove the existing schemas from the publication */
+ PublicationDropSchemas(pubform->oid, delschemas, false);
...
+ }
After more thoughts on it, I still don't think drop all the schemas under " if
(list_length(relations))" is a good idea. I think 1) we'd better keep schema
and relation code separate. 2) if we support other type object(SEQUENCE) I the
future and only SET xx SEQUENCE, I think the above logic won't work because
both relations and schemaidlist will be NIL.
Same as the logic of drop all tables under " if (list_length(schemaidlist))".
Thoughs ?
Best regards,
Hou zj
On Wed, Sep 22, 2021 at 3:02 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v30 patch has the fixes for the same.
Thank you for updating the patches.
Here are random comments on v30-0002 patch:
+
+ if (stmt->action == DEFELEM_SET &&
!list_length(schemaidlist))
+ {
+ delschemas =
GetPublicationSchemas(pubform->oid);
+ LockSchemaList(delschemas);
+ }
I think "list_length(schemaidlist) > 0" would be more readable.
---
This patch introduces some new static functions to publicationcmds.c
but some have function prototypes but some don't. As far as I checked,
ObjectsInPublicationToOids()
CheckObjSchemaNotAlreadyInPublication()
GetAlterPublicationDelRelations()
AlterPublicationSchemas()
CheckPublicationAlterTables()
CheckPublicationAlterSchemas()
LockSchemaList()
OpenReliIdList()
PublicationAddSchemas()
PublicationDropSchemas()
are newly introduced but only four functions:
OpenReliIdList()
LockSchemaList()
PublicationAddSchemas()
PublicationDropSchemas()
have function prototypes. Actually, there already are functions that
don't have their prototype in publicationcmds.c. But it seems better
to clarify what kind of functions don't need to have a prototype at
least in this file.
---
ISTM we can inline the contents of three functions:
GetAlterPublicationDelRelations(), CheckPublicationAlterTables(), and
CheckPublicationAlterSchemas(). These have only one caller and ISTM
makes the readability worse. I think it's not necessary to make
functions for them.
---
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
As this comment mentioned, AlterPublication() calls
AlterPublicationTables() and AlterPublicationSchemas() but this
function also a lot of pre-processing such as building the list and
some checks, depending on stmt->action before calling these two
functions. And those two functions also perform some operation
depending on stmt->action. So ISTM it's better to move those
pre-processing to these two functions and have AlterPublication() just
call these two functions. What do you think?
---
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
Since this function gets all relations in the schema publication, I
think GetAllSchemaPublicationRelations() would be better as a function
name (removing 's' before 'P').
---
+ if (!IsA(node, String))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema
name at or near"),
+
parser_errposition(pstate, pubobj->location));
The error message should mention where the invalid schema name is at
or near. Also, In the following example, the error position in the
error message seems not to be where the invalid schemaname s.s is:
postgres(1:47707)=# create publication p for all tables in schema s.s;
ERROR: invalid schema name at or near
LINE 1: create publication p for all tables in schema s.s;
^
---
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ if (IsA(node, RangeVar))
+ *rels = lappend(*rels, (RangeVar *) node);
+ else if (IsA(node, String))
+ {
+ RangeVar *rel = makeRangeVar(NULL,
strVal(node),
+
pubobj->location);
+
+ *rels = lappend(*rels, rel);
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
(snip)
+ /* Filter out duplicates if user specifies
"sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
Do we need to filter out duplicates also in PUBLICATIONOBJ_TABLE case
since users can specify things like "TABLE tbl, tbl, tbl"?
---
+ if ((action == DEFELEM_ADD || action == DEFELEM_SET) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or
set schemas")));
Why do we require the superuser privilege only for ADD and SET but not for DROP?
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Wed, Sep 22, 2021 at 8:02 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Wednesday, September 22, 2021 2:02 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v30 patch has the fixes for the same.
Thanks for updating the patches.
I have one comment. @@ -474,7 +707,75 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt) ... + if (list_length(relations)) + { ... + /* remove the existing schemas from the publication */ + PublicationDropSchemas(pubform->oid, delschemas, false); ... + }After more thoughts on it, I still don't think drop all the schemas under " if
(list_length(relations))" is a good idea. I think 1) we'd better keep schema
and relation code separate.
How do you suggest changing it?
--
With Regards,
Amit Kapila.
On Wednesday, September 22, 2021 11:22 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
--- + if (!IsA(node, String)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid schema name at or near"), + parser_errposition(pstate, pubobj->location));The error message should mention where the invalid schema name is at
or near. Also, In the following example, the error position in the
error message seems not to be where the invalid schemaname s.s is:postgres(1:47707)=# create publication p for all tables in schema s.s;
ERROR: invalid schema name at or near
LINE 1: create publication p for all tables in schema s.s;
^
I noticed this, too. And I think it could be fixed by the following change, thoughts?
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9681,7 +9681,7 @@ PublicationObjSpec: TABLE pubobj_expr
{
$$ = $5;
$$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
- $$->location = @1;
+ $$->location = @5;
}
| pubobj_expr
{
Besides, about this change in tab-complete.c:
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
It should be "ALL TABLES IN SCHEMA" not "SCHEMA" at the first line, right?
Regards
Tang
On Tue, Sep 21, 2021 at 11:39 PM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Sep 21, 2021 at 9:03 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Sep 17, 2021 at 10:09 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v29 patch has the fixes for the same.
Some minor comments on the v29-0002 patch:
(1)
In get_object_address_publication_schema(), the error message:+ errmsg("publication tables of schema \"%s\" in publication \"%s\"
does not exist",isn't grammatically correct. It should probably be:
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" do
not exist",Modified
I still see the old message in v30. But I have a different suggestion
for this message. How about changing it to: "publication schema \"%s\"
in publication \"%s\" does not exist"? This will make it similar to
other messages and I don't see the need here to add 'tables' as we
have it in grammar.
--
With Regards,
Amit Kapila.
On Wed, Sep 22, 2021 1:29 PMAmit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 22, 2021 at 8:02 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:On Wednesday, September 22, 2021 2:02 AM vignesh C
<vignesh21@gmail.com> wrote:
Attached v30 patch has the fixes for the same.
Thanks for updating the patches.
I have one comment. @@ -474,7 +707,75 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt) ... + if (list_length(relations)) + { ... + /* remove the existing schemas from the publication*/
+ PublicationDropSchemas(pubform->oid, + delschemas, false); ... + }After more thoughts on it, I still don't think drop all the schemas
under " if (list_length(relations))" is a good idea. I think 1) we'd
better keep schema and relation code separate.How do you suggest changing it?
Personally, I think we'd better move the code about changing publication's
tablelist into AlterPublicationTables and the code about changing publication's
schemalist into AlterPublicationSchemas. It's similar to what the v29-patchset
did, the difference is the SET action, I suggest we drop all the tables in
function AlterPublicationTables when user only set schemas and drop all the
schema in AlterPublicationSchemas when user only set tables. In this approach,
we can keep schema and relation code separate and don't need to worry
about the locking order.
Attach a top-up patch which refactor the code like above.
Thoughts ?
Best regards,
Hou zj
Attachments:
0001-schema-refactor_patchapplication/octet-stream; name=0001-schema-refactor_patchDownload
From 9716e097747ca9797bada35de5ace3c078857115 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 22 Sep 2021 19:08:07 +0800
Subject: [PATCH] schema refactor
---
src/backend/commands/publicationcmds.c | 276 +++++++++++++--------------------
1 file changed, 110 insertions(+), 166 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 68be61a..e6af432 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -513,20 +513,79 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, List *rels,
- List *delrels)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
+ List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- Assert(list_length(rels) > 0);
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ rels = OpenTableList(tables);
if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
+
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
+ }
else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
+ List *oldrelids = GetPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
+ List *delrels = NIL;
+ ListCell *oldlc;
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
+ /* Calculate which relations to drop. */
+ foreach(oldlc, oldrelids)
+ {
+ Oid oldrelid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, rels)
+ {
+ Relation newrel = (Relation) lfirst(newlc);
+
+ if (RelationGetRelid(newrel) == oldrelid)
+ {
+ found = true;
+ break;
+ }
+ }
+ /* Not yet in the list, open it and add to the list */
+ if (!found)
+ {
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ /* And drop them. */
PublicationDropTables(pubid, delrels, true);
/*
@@ -534,64 +593,72 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, List *rels,
* skip existing ones when doing catalog update.
*/
PublicationAddTables(pubid, rels, true, stmt);
+
+ CloseTableList(delrels);
}
+
+ CloseTableList(rels);
}
/*
- * Get the relations that should be deleted for the publication.
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
*/
static void
-GetAlterPublicationDelRelations(DefElemAction action, Oid pubid, List *rels,
- List **delrels)
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
{
- List *oldrelids;
- ListCell *oldlc;
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
- if (action != DEFELEM_SET)
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
return;
- oldrelids = GetPublicationRelations(pubid, PUBLICATION_PART_ROOT);
+ /* Check that user is allowed to manipulate the publication tables */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ if (schemaidlist && (stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
- /* Calculate which relations to drop. */
- foreach(oldlc, oldrelids)
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
{
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- bool found = false;
+ List *rels;
+ List *reloids;
- foreach(newlc, rels)
- {
- Relation newrel = (Relation) lfirst(newlc);
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
- if (RelationGetRelid(newrel) == oldrelid)
- {
- found = true;
- break;
- }
- }
- /* Not yet in the list, open it and add to the list */
- if (!found)
- *delrels = lappend_oid(*delrels, oldrelid);
- }
-}
-
-/*
- * Alter the publication schemas.
- *
- * Add/Remove/Set all tables from schemas to/from publication.
- */
-static void
-AlterPublicationSchemas(AlterPublicationStmt *stmt, HeapTuple tup,
- List *schemaidlist, List *delschemas)
-{
- Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
- if (stmt->action == DEFELEM_ADD)
+ CloseTableList(rels);
PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
else if (stmt->action == DEFELEM_DROP)
PublicationDropSchemas(pubform->oid, schemaidlist, false);
else /* DEFELEM_SET */
{
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+ LockSchemaList(delschemas);
+
/* And drop them */
PublicationDropSchemas(pubform->oid, delschemas, true);
@@ -606,74 +673,6 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, HeapTuple tup,
}
/*
- * Check if relations can be in given publication and throws appropriate
- * error if not.
- */
-static void
-CheckPublicationAlterTables(DefElemAction action, HeapTuple tup,
- List *rels, List *schemaidlist)
-{
- Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
-
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
-
- if (action == DEFELEM_ADD)
- {
- /*
- * Check if the relation is member of the existing schema in the
- * publication or member of the schema list specified.
- */
- List *schemas = list_concat(schemaidlist,
- GetPublicationSchemas(pubform->oid));
-
- CheckObjSchemaNotAlreadyInPublication(rels, schemas,
- PUBLICATIONOBJ_TABLE);
- }
- else if (action == DEFELEM_SET && list_length(schemaidlist))
- {
- /* check if relation is member of the schema list specified */
- CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
- PUBLICATIONOBJ_TABLE);
- }
-}
-
-/*
- * Check if schemas can be in given publication and throws appropriate
- * error if not.
- */
-static void
-CheckPublicationAlterSchemas(DefElemAction action, HeapTuple tup,
- List *schemaidlist, List *relations)
-{
- Form_pg_publication pubform;
-
- pubform = (Form_pg_publication) GETSTRUCT(tup);
-
- /* Check that user is allowed to manipulate the publication tables */
- if (pubform->puballtables)
- ereport(ERROR,
- errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications."));
-
- if ((action == DEFELEM_ADD || action == DEFELEM_SET) && !superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to add or set schemas")));
-
- if (action == DEFELEM_ADD)
- CheckObjSchemaNotAlreadyInPublication(relations, schemaidlist,
- PUBLICATIONOBJ_REL_IN_SCHEMA);
-}
-
-/*
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
@@ -718,63 +717,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
AccessExclusiveLock);
- if (list_length(relations))
- {
- List *rels;
- List *delschemas = NIL;
- List *delrelids = NIL;
- List *delrels = NIL;
-
- if (stmt->action == DEFELEM_SET && !list_length(schemaidlist))
- {
- delschemas = GetPublicationSchemas(pubform->oid);
- LockSchemaList(delschemas);
- }
-
- rels = OpenTableList(relations);
- GetAlterPublicationDelRelations(stmt->action, pubform->oid, rels,
- &delrelids);
-
- CheckPublicationAlterTables(stmt->action, tup, rels, schemaidlist);
- delrels = OpenReliIdList(delrelids);
-
- /* remove the existing schemas from the publication */
- PublicationDropSchemas(pubform->oid, delschemas, false);
-
- AlterPublicationTables(stmt, tup, rels, delrels);
- CloseTableList(delrels);
- CloseTableList(rels);
- }
-
- if (list_length(schemaidlist))
- {
- List *pubrelids = NIL;
- List *pubrels = NIL;
- List *delschemas = NIL;
-
- if (stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET)
- pubrelids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ROOT);
-
- if (stmt->action == DEFELEM_SET)
- {
- List *oldschemaids = GetPublicationSchemas(pubform->oid);
-
- delschemas = list_difference_oid(oldschemaids, schemaidlist);
- LockSchemaList(delschemas);
- }
-
- LockSchemaList(schemaidlist);
- pubrels = OpenReliIdList(pubrelids);
- CheckPublicationAlterSchemas(stmt->action, tup, schemaidlist,
- pubrels);
-
- if (stmt->action == DEFELEM_SET && !list_length(relations))
- PublicationDropTables(pubform->oid, pubrels, false);
-
- CloseTableList(pubrels);
- AlterPublicationSchemas(stmt, tup, schemaidlist, delschemas);
- }
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
}
/* Cleanup. */
--
2.7.2.windows.1
On Wed, Sep 22, 2021 at 9:33 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
How do you suggest changing it?
Personally, I think we'd better move the code about changing publication's
tablelist into AlterPublicationTables and the code about changing publication's
schemalist into AlterPublicationSchemas. It's similar to what the v29-patchset
did, the difference is the SET action, I suggest we drop all the tables in
function AlterPublicationTables when user only set schemas and drop all the
schema in AlterPublicationSchemas when user only set tables. In this approach,
we can keep schema and relation code separate and don't need to worry
about the locking order.Attach a top-up patch which refactor the code like above.
Thoughts ?
Sounds like a good idea.
Is it possible to incorporate the existing
CheckPublicationAlterTables() and CheckPublicationAlterSchemas()
functions into your suggested update?
I think it might tidy up the error-checking a bit.
Regards,
Greg Nancarrow
Fujitsu Australia
On Wed, Sep 22, 2021 at 5:03 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Personally, I think we'd better move the code about changing publication's
tablelist into AlterPublicationTables and the code about changing publication's
schemalist into AlterPublicationSchemas. It's similar to what the v29-patchset
did, the difference is the SET action, I suggest we drop all the tables in
function AlterPublicationTables when user only set schemas and drop all the
schema in AlterPublicationSchemas when user only set tables. In this approach,
we can keep schema and relation code separate and don't need to worry
about the locking order.Attach a top-up patch which refactor the code like above.
Good suggestion. I think it would still be better if we can move the
checks related to superuser and puballtables into a separate function
that gets called before taking a lock on publication.
--
With Regards,
Amit Kapila.
On Wed, Sep 22, 2021 at 8:52 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Wed, Sep 22, 2021 at 3:02 AM vignesh C <vignesh21@gmail.com> wrote:
---
This patch introduces some new static functions to publicationcmds.c
but some have function prototypes but some don't. As far as I checked,ObjectsInPublicationToOids()
CheckObjSchemaNotAlreadyInPublication()
GetAlterPublicationDelRelations()
AlterPublicationSchemas()
CheckPublicationAlterTables()
CheckPublicationAlterSchemas()
LockSchemaList()
OpenReliIdList()
PublicationAddSchemas()
PublicationDropSchemas()are newly introduced but only four functions:
OpenReliIdList()
LockSchemaList()
PublicationAddSchemas()
PublicationDropSchemas()have function prototypes. Actually, there already are functions that
don't have their prototype in publicationcmds.c. But it seems better
to clarify what kind of functions don't need to have a prototype at
least in this file.
I think if the function is defined after its use then we declare it at
the top. Do you prefer to declare all static functions to allow ease
of usage? Do you have something else in mind?
--- + if ((action == DEFELEM_ADD || action == DEFELEM_SET) && !superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to add or set schemas")));Why do we require the superuser privilege only for ADD and SET but not for DROP?
For Add/Set of for all tables of Schema is similar to all tables
publication requirement. For Drop, I don't think it is mandatory to
allow only to superuser. The same applies to Alter Publication ...
Drop table case where you don't need to be table owner whereas, for
Add, you need to be. We had a discussion on these points in this
thread. See [1]/messages/by-id/CAA4eK1KqhUBHbcpT92VMPvUUDgGvyOK0ekXOwjNR6L=Y_bcsGw@mail.gmail.com and some emails prior to it.
[1]: /messages/by-id/CAA4eK1KqhUBHbcpT92VMPvUUDgGvyOK0ekXOwjNR6L=Y_bcsGw@mail.gmail.com
--
With Regards,
Amit Kapila.
From Thurs, Sep 23, 2021 12:09 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 22, 2021 at 5:03 PM Hou Zhijie <houzj.fnst@fujitsu.com> wrote:
Personally, I think we'd better move the code about changing publication's
tablelist into AlterPublicationTables and the code about changingpublication's
schemalist into AlterPublicationSchemas. It's similar to what the
v29-patchset
did, the difference is the SET action, I suggest we drop all the tables in
function AlterPublicationTables when user only set schemas and drop all the
schema in AlterPublicationSchemas when user only set tables. In thisapproach,
we can keep schema and relation code separate and don't need to worry
about the locking order.Attach a top-up patch which refactor the code like above.
Good suggestion. I think it would still be better if we can move the
checks related to superuser and puballtables into a separate function
that gets called before taking a lock on publication.
I agreed.
I noticed v30-0001 has been committed with some minor changes, and the V30-0002
patchset need to be rebased accordingly. Attach a rebased version patch set to
make cfbot happy. Also Attach the two top-up patches which refactor the code as
suggested. (top-up patch 1 is to keep schema and table code separate, top-up
patch 2 is to move some cheap check into a function and invoke it before
locking.)
Best regards,
Hou zj
Attachments:
top-up-1-schema-refactor.patchapplication/octet-stream; name=top-up-1-schema-refactor.patchDownload
From 9716e097747ca9797bada35de5ace3c078857115 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 22 Sep 2021 19:08:07 +0800
Subject: [PATCH] schema refactor
---
src/backend/commands/publicationcmds.c | 276 +++++++++++++--------------------
1 file changed, 110 insertions(+), 166 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 68be61a..e6af432 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -513,20 +513,79 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, List *rels,
- List *delrels)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
+ List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- Assert(list_length(rels) > 0);
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ rels = OpenTableList(tables);
if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
+
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
+ }
else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
+ List *oldrelids = GetPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
+ List *delrels = NIL;
+ ListCell *oldlc;
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
+ /* Calculate which relations to drop. */
+ foreach(oldlc, oldrelids)
+ {
+ Oid oldrelid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, rels)
+ {
+ Relation newrel = (Relation) lfirst(newlc);
+
+ if (RelationGetRelid(newrel) == oldrelid)
+ {
+ found = true;
+ break;
+ }
+ }
+ /* Not yet in the list, open it and add to the list */
+ if (!found)
+ {
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ /* And drop them. */
PublicationDropTables(pubid, delrels, true);
/*
@@ -534,64 +593,72 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, List *rels,
* skip existing ones when doing catalog update.
*/
PublicationAddTables(pubid, rels, true, stmt);
+
+ CloseTableList(delrels);
}
+
+ CloseTableList(rels);
}
/*
- * Get the relations that should be deleted for the publication.
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
*/
static void
-GetAlterPublicationDelRelations(DefElemAction action, Oid pubid, List *rels,
- List **delrels)
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
{
- List *oldrelids;
- ListCell *oldlc;
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
- if (action != DEFELEM_SET)
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
return;
- oldrelids = GetPublicationRelations(pubid, PUBLICATION_PART_ROOT);
+ /* Check that user is allowed to manipulate the publication tables */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ if (schemaidlist && (stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
- /* Calculate which relations to drop. */
- foreach(oldlc, oldrelids)
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
{
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- bool found = false;
+ List *rels;
+ List *reloids;
- foreach(newlc, rels)
- {
- Relation newrel = (Relation) lfirst(newlc);
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
- if (RelationGetRelid(newrel) == oldrelid)
- {
- found = true;
- break;
- }
- }
- /* Not yet in the list, open it and add to the list */
- if (!found)
- *delrels = lappend_oid(*delrels, oldrelid);
- }
-}
-
-/*
- * Alter the publication schemas.
- *
- * Add/Remove/Set all tables from schemas to/from publication.
- */
-static void
-AlterPublicationSchemas(AlterPublicationStmt *stmt, HeapTuple tup,
- List *schemaidlist, List *delschemas)
-{
- Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
- if (stmt->action == DEFELEM_ADD)
+ CloseTableList(rels);
PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
else if (stmt->action == DEFELEM_DROP)
PublicationDropSchemas(pubform->oid, schemaidlist, false);
else /* DEFELEM_SET */
{
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+ LockSchemaList(delschemas);
+
/* And drop them */
PublicationDropSchemas(pubform->oid, delschemas, true);
@@ -606,74 +673,6 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt, HeapTuple tup,
}
/*
- * Check if relations can be in given publication and throws appropriate
- * error if not.
- */
-static void
-CheckPublicationAlterTables(DefElemAction action, HeapTuple tup,
- List *rels, List *schemaidlist)
-{
- Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
-
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
-
- if (action == DEFELEM_ADD)
- {
- /*
- * Check if the relation is member of the existing schema in the
- * publication or member of the schema list specified.
- */
- List *schemas = list_concat(schemaidlist,
- GetPublicationSchemas(pubform->oid));
-
- CheckObjSchemaNotAlreadyInPublication(rels, schemas,
- PUBLICATIONOBJ_TABLE);
- }
- else if (action == DEFELEM_SET && list_length(schemaidlist))
- {
- /* check if relation is member of the schema list specified */
- CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
- PUBLICATIONOBJ_TABLE);
- }
-}
-
-/*
- * Check if schemas can be in given publication and throws appropriate
- * error if not.
- */
-static void
-CheckPublicationAlterSchemas(DefElemAction action, HeapTuple tup,
- List *schemaidlist, List *relations)
-{
- Form_pg_publication pubform;
-
- pubform = (Form_pg_publication) GETSTRUCT(tup);
-
- /* Check that user is allowed to manipulate the publication tables */
- if (pubform->puballtables)
- ereport(ERROR,
- errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications."));
-
- if ((action == DEFELEM_ADD || action == DEFELEM_SET) && !superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to add or set schemas")));
-
- if (action == DEFELEM_ADD)
- CheckObjSchemaNotAlreadyInPublication(relations, schemaidlist,
- PUBLICATIONOBJ_REL_IN_SCHEMA);
-}
-
-/*
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
@@ -718,63 +717,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
AccessExclusiveLock);
- if (list_length(relations))
- {
- List *rels;
- List *delschemas = NIL;
- List *delrelids = NIL;
- List *delrels = NIL;
-
- if (stmt->action == DEFELEM_SET && !list_length(schemaidlist))
- {
- delschemas = GetPublicationSchemas(pubform->oid);
- LockSchemaList(delschemas);
- }
-
- rels = OpenTableList(relations);
- GetAlterPublicationDelRelations(stmt->action, pubform->oid, rels,
- &delrelids);
-
- CheckPublicationAlterTables(stmt->action, tup, rels, schemaidlist);
- delrels = OpenReliIdList(delrelids);
-
- /* remove the existing schemas from the publication */
- PublicationDropSchemas(pubform->oid, delschemas, false);
-
- AlterPublicationTables(stmt, tup, rels, delrels);
- CloseTableList(delrels);
- CloseTableList(rels);
- }
-
- if (list_length(schemaidlist))
- {
- List *pubrelids = NIL;
- List *pubrels = NIL;
- List *delschemas = NIL;
-
- if (stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET)
- pubrelids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ROOT);
-
- if (stmt->action == DEFELEM_SET)
- {
- List *oldschemaids = GetPublicationSchemas(pubform->oid);
-
- delschemas = list_difference_oid(oldschemaids, schemaidlist);
- LockSchemaList(delschemas);
- }
-
- LockSchemaList(schemaidlist);
- pubrels = OpenReliIdList(pubrelids);
- CheckPublicationAlterSchemas(stmt->action, tup, schemaidlist,
- pubrels);
-
- if (stmt->action == DEFELEM_SET && !list_length(relations))
- PublicationDropTables(pubform->oid, pubrels, false);
-
- CloseTableList(pubrels);
- AlterPublicationSchemas(stmt, tup, schemaidlist, delschemas);
- }
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
}
/* Cleanup. */
--
2.7.2.windows.1
top-up-2-move-check-into-a-separate-function.patchapplication/octet-stream; name=top-up-2-move-check-into-a-separate-function.patchDownload
From 298869f57494660ac08b07414ef5e3b553b7b94c Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Thu, 23 Sep 2021 14:32:14 +0800
Subject: [PATCH] move check into a separate function
---
src/backend/commands/publicationcmds.c | 57 +++++++++++++++++++++-------------
1 file changed, 35 insertions(+), 22 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 2153578..9ecae0d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -523,14 +523,6 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != DEFELEM_SET)
return;
- /* Check that user is allowed to manipulate the publication tables. */
- if (tables && pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
-
rels = OpenTableList(tables);
if (stmt->action == DEFELEM_ADD)
@@ -614,20 +606,6 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
if (!schemaidlist && stmt->action != DEFELEM_SET)
return;
- /* Check that user is allowed to manipulate the publication tables */
- if (schemaidlist && pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
-
- if (schemaidlist && (stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
- !superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to add or set schemas")));
-
/*
* Schema lock is held until the publication is altered to prevent
* concurrent schema deletion. No need to unlock the schemas, the locks
@@ -673,6 +651,39 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
}
/*
+ * Check if relations and schemas can be in given publication and throws
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /* Check that user is allowed to manipulate the publication tables in schema */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
+/*
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
@@ -713,6 +724,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&schemaidlist);
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
/* Lock the publication so nobody else can do anything with it. */
LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
AccessExclusiveLock);
--
2.7.2.windows.1
v31-0001-Added-schema-level-support-for-publication.patchapplication/octet-stream; name=v31-0001-Added-schema-level-support-for-publication.patchDownload
From 0129ced57f63ec43ef9fc5fd38897775da0e4407 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@fujitsu.com>
Date: Thu, 23 Sep 2021 13:40:27 +0800
Subject: [PATCH] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 296 ++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 598 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 20 +-
src/backend/nodes/equalfuncs.c | 15 +-
src/backend/parser/gram.y | 215 +++++--
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 15 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 36 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1276 insertions(+), 201 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..ed442c33c5 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *pubname;
+ Publication *pub;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..c9cc4afe74 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -152,7 +177,7 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -162,14 +187,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,84 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -328,6 +431,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -366,7 +536,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -428,6 +598,100 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -557,10 +821,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemasPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..6671983f4b 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,123 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ Node *node;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+ node = (Node *) pubobj->object;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ if (IsA(node, RangeVar))
+ *rels = lappend(*rels, (RangeVar *) node);
+ else if (IsA(node, String))
+ {
+ RangeVar *rel = makeRangeVar(NULL, strVal(node),
+ pubobj->location);
+
+ *rels = lappend(*rels, rel);
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ Oid schemaid;
+ char *schemaname;
+
+ if (!IsA(node, String))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(node);
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+ }
+ else
+ schemaid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +277,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,17 +348,36 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- else if (stmt->for_all_tables)
+
+ if (schemaidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ Assert(list_length(schemaidlist) > 0);
+
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
@@ -318,13 +464,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemasPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,90 +513,171 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup, List *rels,
+ List *delrels)
{
- List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ Assert(list_length(rels) > 0);
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
-
- if (stmt->tableAction == DEFELEM_ADD)
+ if (stmt->action == DEFELEM_ADD)
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
- List *oldrelids = GetPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- List *delrels = NIL;
- ListCell *oldlc;
+ PublicationDropTables(pubid, delrels, true);
- /* Calculate which relations to drop. */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- bool found = false;
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddTables(pubid, rels, true, stmt);
+ }
+}
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
- {
- found = true;
- break;
- }
- }
- /* Not yet in the list, open it and add to the list */
- if (!found)
- {
- Relation oldrel;
- PublicationRelInfo *pubrel;
+/*
+ * Get the relations that should be deleted for the publication.
+ */
+static void
+GetAlterPublicationDelRelations(DefElemAction action, Oid pubid, List *rels,
+ List **delrels)
+{
+ List *oldrelids;
+ ListCell *oldlc;
+
+ if (action != DEFELEM_SET)
+ return;
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
+ oldrelids = GetPublicationRelations(pubid, PUBLICATION_PART_ROOT);
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ /* Calculate which relations to drop. */
+ foreach(oldlc, oldrelids)
+ {
+ Oid oldrelid = lfirst_oid(oldlc);
+ ListCell *newlc;
+ bool found = false;
+
+ foreach(newlc, rels)
+ {
+ Relation newrel = (Relation) lfirst(newlc);
- delrels = lappend(delrels, pubrel);
+ if (RelationGetRelid(newrel) == oldrelid)
+ {
+ found = true;
+ break;
}
}
+ /* Not yet in the list, open it and add to the list */
+ if (!found)
+ *delrels = lappend_oid(*delrels, oldrelid);
+ }
+}
- /* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *schemaidlist, List *delschemas)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if (stmt->action == DEFELEM_ADD)
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+CheckPublicationAlterTables(DefElemAction action, HeapTuple tup,
+ List *rels, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+
+ if (action == DEFELEM_ADD)
+ {
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ List *schemas = list_concat(schemaidlist,
+ GetPublicationSchemas(pubform->oid));
- CloseTableList(delrels);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
+ }
+ else if (action == DEFELEM_SET && list_length(schemaidlist))
+ {
+ /* check if relation is member of the schema list specified */
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
}
+}
- CloseTableList(rels);
+/*
+ * Check if schemas can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+CheckPublicationAlterSchemas(DefElemAction action, HeapTuple tup,
+ List *schemaidlist, List *relations)
+{
+ Form_pg_publication pubform;
+
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /* Check that user is allowed to manipulate the publication tables */
+ if (pubform->puballtables)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications."));
+
+ if ((action == DEFELEM_ADD || action == DEFELEM_SET) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ if (action == DEFELEM_ADD)
+ CheckObjSchemaNotAlreadyInPublication(relations, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
}
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +707,75 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ /* Lock the publication so nobody else can do anything with it. */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ if (list_length(relations))
+ {
+ List *rels;
+ List *delschemas = NIL;
+ List *delrelids = NIL;
+ List *delrels = NIL;
+
+ if (stmt->action == DEFELEM_SET && !list_length(schemaidlist))
+ {
+ delschemas = GetPublicationSchemas(pubform->oid);
+ LockSchemaList(delschemas);
+ }
+
+ rels = OpenTableList(relations);
+ GetAlterPublicationDelRelations(stmt->action, pubform->oid, rels,
+ &delrelids);
+
+ CheckPublicationAlterTables(stmt->action, tup, rels, schemaidlist);
+ delrels = OpenReliIdList(delrelids);
+
+ /* remove the existing schemas from the publication */
+ PublicationDropSchemas(pubform->oid, delschemas, false);
+
+ AlterPublicationTables(stmt, tup, rels, delrels);
+ CloseTableList(delrels);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist))
+ {
+ List *pubrelids = NIL;
+ List *pubrels = NIL;
+ List *delschemas = NIL;
+
+ if (stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET)
+ pubrelids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ROOT);
+
+ if (stmt->action == DEFELEM_SET)
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+ LockSchemaList(delschemas);
+ }
+
+ LockSchemaList(schemaidlist);
+ pubrels = OpenReliIdList(pubrelids);
+ CheckPublicationAlterSchemas(stmt->action, tup, schemaidlist,
+ pubrels);
+
+ if (stmt->action == DEFELEM_SET && !list_length(relations))
+ PublicationDropTables(pubform->oid, pubrels, false);
+
+ CloseTableList(pubrels);
+ AlterPublicationSchemas(stmt, tup, schemaidlist, delschemas);
+ }
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -538,7 +839,7 @@ RemovePublicationById(Oid pubid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication %u", pubid);
- pubform = (Form_pg_publication)GETSTRUCT(tup);
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate relcache so that publication info is rebuilt. */
if (pubform->puballtables)
@@ -552,9 +853,84 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to prevent concurrent schema deletion. No need to unlock the
+ * schemas, the locks will be released at the end of the command.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +944,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +968,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +1001,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +1022,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +1041,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1049,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1061,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1101,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1123,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..f91b9963c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..ade93023b8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4817,7 +4817,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4830,9 +4830,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4958,12 +4958,14 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_NODE_FIELD(object);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -5887,8 +5889,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..1dcd63d64f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2302,7 +2302,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2314,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3134,9 +3134,10 @@ _equalBitString(const BitString *a, const BitString *b)
}
static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
{
- COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(object);
return true;
}
@@ -3894,8 +3895,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..277c6c513e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,6 +195,9 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
@@ -256,6 +259,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +429,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +520,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -554,6 +558,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
+%type <node> pubobj_name
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,69 +9598,119 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [[, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
+pubobj_expr:
+ pubobj_name
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
+ }
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)$1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)makeString("CURRENT_SCHEMA");
+ }
;
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * This can be either a schema or relation name. For relations, the inheritance
+ * will be implicit.
+ */
+pubobj_name:
+ ColId
{
- $$ = (Node *) $3;
+ $$ = (Node *)makeString($1);
}
- | FOR ALL TABLES
+ | ColId indirection
{
- $$ = (Node *) makeInteger(true);
+ $$ = (Node *)makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
-publication_table_list:
- publication_table
- { $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->location = @1;
+ }
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->location = @1;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
;
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9722,28 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12487,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -12453,7 +12517,6 @@ relation_expr:
}
;
-
relation_expr_list:
relation_expr { $$ = list_make1($1); }
| relation_expr_list ',' relation_expr { $$ = lappend($1, $3); }
@@ -15104,28 +15167,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17045,6 +17087,41 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r = makeRangeVar(NULL, NULL, location);
+
+ check_qualified_name(namelist, yyscanner);
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..9a4f92c81a 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -114,10 +109,18 @@ extern List *GetAllTablesPublicationRelations(bool pubviaroot);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemasPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..8220c72469 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -487,7 +488,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..15aacf7165 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ Node *object; /* publication object could be:
+ * RangeVar - table object
+ * String - tablename or schemaname */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 402a6617a9..bfc6909a43 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.18.4
v31-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchapplication/octet-stream; name=v31-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 4b2d98d818bbb0e4138ff27489c6eb710a8f9d1f Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v30 3/6] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 +++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 192 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 33 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 347 insertions(+), 51 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..93ed3344d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4126,6 +4134,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4213,6 +4309,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10619,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18854,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..953e1f52cf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6407,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6443,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6453,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6479,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..6262b74b9e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,27 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bfc6909a43..80510a9e1a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v31-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchapplication/octet-stream; name=v31-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 7967cb5ef0373911e70c4332ee59e4041e8fadaf Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v30 4/6] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 483 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 229 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
6 files changed, 914 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index cad1b374be..037da6c793 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,89 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+ERROR: relation "testpub_nopk" is not part of the publication
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- 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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +177,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -261,18 +344,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -304,11 +390,404 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 04b34ee299..a3860091dd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -147,9 +180,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -157,12 +192,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -183,11 +218,201 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v31-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchapplication/octet-stream; name=v31-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 592f526b788f2c11c0f7b2fe64a3d660d393ab5a Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v30 5/6] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 89 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 +++++++++++++++++--
3 files changed, 208 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2f0def9b19..c18a90a691 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..af7c946498 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,17 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables and/or all tables in schema are
+ part of the publication. The <literal>SET</literal> clause will replace
+ the list of tables and/or all tables in schema in the publication with the
+ specified one, the existing tables and all tables in schema that were
+ present in the publication will be removed. The <literal>ADD</literal>
+ clause will add one or more tables and/or all tables in schema to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables and/or all tables in schema from the publication. Note that adding
+ tables and/or all tables in schema to a publication that is already
+ subscribed to will require a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal>
+ action on the subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +71,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +118,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +171,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..0ca7e7c0e6 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of schema specified in
+ <literal>FOR ALL TABLES IN SCHEMA</literal> option is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with schema's table specified as part of
+ <literal>FOR TABLE</literal> option is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v31-0005-Implemented-pg_publication_objects-view.patchapplication/octet-stream; name=v31-0005-Implemented-pg_publication_objects-view.patchDownload
From c229c3586bbc428a66156536584f13a45b7ff4fb Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v30 6/6] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c18a90a691..38293cbdba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Thur, Sep 23, 2021 11:06 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Wed, Sep 22, 2021 at 9:33 PM houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com> wrote:
How do you suggest changing it?
Personally, I think we'd better move the code about changing
publication's tablelist into AlterPublicationTables and the code about
changing publication's schemalist into AlterPublicationSchemas. It's
similar to what the v29-patchset did, the difference is the SET
action, I suggest we drop all the tables in function
AlterPublicationTables when user only set schemas and drop all the
schema in AlterPublicationSchemas when user only set tables. In this
approach, we can keep schema and relation code separate and don't need toworry about the locking order.
Attach a top-up patch which refactor the code like above.
Thoughts ?Sounds like a good idea.
Is it possible to incorporate the existing
CheckPublicationAlterTables() and CheckPublicationAlterSchemas() functions
into your suggested update?
I think it might tidy up the error-checking a bit.
I agreed we can put the check about ALL TABLE and superuser into a function
like what the v30-patchset did. But I have some hesitations about the code
related to CheckObjSchemaNotAlreadyInPublication(). Currently, we need to open
and lock the table before invoking the CheckObjxxx function, ISTM we'd better
open the table in function AlterPublicationTables. Maybe we can wait for the
author's(Vignesh) opinion.
Best regards,
Hou zj
On Thu, Sep 23, 2021 at 5:02 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Sounds like a good idea.
Is it possible to incorporate the existing
CheckPublicationAlterTables() and CheckPublicationAlterSchemas() functions
into your suggested update?
I think it might tidy up the error-checking a bit.I agreed we can put the check about ALL TABLE and superuser into a function
like what the v30-patchset did. But I have some hesitations about the code
related to CheckObjSchemaNotAlreadyInPublication(). Currently, we need to open
and lock the table before invoking the CheckObjxxx function, ISTM we'd better
open the table in function AlterPublicationTables. Maybe we can wait for the
author's(Vignesh) opinion.
Yes, I think you're right, the code related to
CheckObjSchemaNotAlreadyInPublication() should be left where it is
(according to your schema refactor patch).
Regards,
Greg Nancarrow
Fujitsu Australia
On Wed, Sep 22, 2021 at 6:57 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Wed, Sep 22, 2021 at 4:02 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v30 patch has the fixes for the same.
Thanks for all the patch updates.
I have some suggested updates to the v30-0005 documentation patch:doc/src/sgml/ref/alter_publication.sgml
(1)
I'm thinking it might be better to simplify the description, because
it's a bit wordy and difficult to read with the "all tables in schema"
bits.
Suggested update is below (thoughts?):BEFORE: + The first three variants change which tables and/or all tables in schema are + part of the publication. The <literal>SET</literal> clause will replace + the list of tables and/or all tables in schema in the publication with the + specified one, the existing tables and all tables in schema that were + present in the publication will be removed. The <literal>ADD</literal> + clause will add one or more tables and/or all tables in schema to the + publication. The <literal>DROP</literal> clauses will remove one or more + tables and/or all tables in schema from the publication. Note that adding + tables and/or all tables in schema to a publication that is already + subscribed to will require a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> + action on the subscribing side in order to become effective. AFTER: + The first three variants change which tables/schemas are + part of the publication. The <literal>SET</literal> clause will replace + the list of tables/schemas in the publication with the + specified list; the existing tables/schemas that were + present in the publication will be removed. The <literal>ADD</literal> + clause will add one or more tables/schemas to the + publication. The <literal>DROP</literal> clauses will remove one or more + tables/schemas from the publication. Note that adding + tables/schemas to a publication that is already + subscribed to will require a <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> + action on the subscribing side in order to become effective.
Modified
doc/src/sgml/ref/create_publication.sgml
(2)
I suggest an update similar to the following:BEFORE: + Specifying a table that is part of schema specified in + <literal>FOR ALL TABLES IN SCHEMA</literal> option is not supported. AFTER: + Specifying a table that is part of a schema already included in the publication is not supported.
This doc content is for the following example:
create publication pub1 for all tables in schema sch1, table sch1.t1;
It is about the schema specified in all tables in schema option along
with table option. I think the existing content is better.
(3)
I find the following description a little unclear:+ <para> + Specifying a schema along with schema's table specified as part of + <literal>FOR TABLE</literal> option is not supported. + </para>Perhaps the following would be better:
+ <para> + Specifying a schema that contains a table already included in the + publication is not supported. + </para>
Similar to above
(4) Minor fix: BEFORE: + rights on the table. The <command>FOR ALL TABLES</command> and + <command>FOR ALL TABLES IN SCHEMA</command> clause requires the invoking + user to be a superuser. AFTER: + rights on the table. The <command>FOR ALL TABLES</command> and + <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking + user to be a superuser.
Modified
Attached v32 patch has the fixes for the same.
Regards,
Vignesh
Attachments:
v32-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v32-0001-Added-schema-level-support-for-publication.patchDownload
From 5230e4857b2f01704d3101c7fbc38241445113f9 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 23 Sep 2021 13:40:27 +0800
Subject: [PATCH v32 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 ++++++
src/backend/catalog/pg_publication.c | 296 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 501 ++++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 20 +-
src/backend/nodes/equalfuncs.c | 15 +-
src/backend/parser/gram.y | 214 +++++---
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 15 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 36 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1206 insertions(+), 173 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..04e785b192 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -152,7 +177,7 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -162,14 +187,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,84 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -328,6 +431,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -366,7 +536,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -428,6 +598,100 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -557,10 +821,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..d7cefc3515 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,123 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pstate, pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ Node *node;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+ node = (Node *) pubobj->object;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+ else
+ prevobjtype = pubobj->pubobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ if (IsA(node, RangeVar))
+ *rels = lappend(*rels, (RangeVar *) node);
+ else if (IsA(node, String))
+ {
+ RangeVar *rel = makeRangeVar(NULL, strVal(node),
+ pubobj->location);
+
+ *rels = lappend(*rels, rel);
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ Oid schemaid;
+ char *schemaname;
+
+ if (!IsA(node, String))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pstate, pubobj->location));
+
+ schemaname = strVal(node);
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+ }
+ else
+ schemaid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +277,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,17 +348,36 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- else if (stmt->for_all_tables)
+
+ if (schemaidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ Assert(list_length(schemaidlist) > 0);
+
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
@@ -318,13 +464,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +513,32 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +547,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +559,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +570,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +592,102 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in given publication and throws
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /* Check that user is allowed to manipulate the publication tables in schema */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +717,22 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /* Lock the publication so nobody else can do anything with it. */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -538,7 +796,7 @@ RemovePublicationById(Oid pubid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication %u", pubid);
- pubform = (Form_pg_publication)GETSTRUCT(tup);
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate relcache so that publication info is rebuilt. */
if (pubform->puballtables)
@@ -552,9 +810,84 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to prevent concurrent schema deletion. No need to unlock the
+ * schemas, the locks will be released at the end of the command.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +901,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +925,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +958,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +979,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +998,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1006,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1018,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1058,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1080,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..f91b9963c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..ade93023b8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4817,7 +4817,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4830,9 +4830,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4958,12 +4958,14 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_NODE_FIELD(object);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -5887,8 +5889,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..1dcd63d64f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2302,7 +2302,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2314,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3134,9 +3134,10 @@ _equalBitString(const BitString *a, const BitString *b)
}
static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
{
- COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(object);
return true;
}
@@ -3894,8 +3895,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..3f518c027d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,6 +195,9 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
@@ -256,6 +259,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +429,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +520,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -554,6 +558,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
+%type <node> pubobj_name
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,69 +9598,119 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [[, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
+pubobj_expr:
+ pubobj_name
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
+ $$->location = @1;
+ }
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)$1;
+ $$->location = @1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)makeString("CURRENT_SCHEMA");
+ $$->location = @1;
+ }
;
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * This can be either a schema or relation name. For relations, the inheritance
+ * will be implicit.
+ */
+pubobj_name:
+ ColId
{
- $$ = (Node *) $3;
+ $$ = (Node *)makeString($1);
}
- | FOR ALL TABLES
+ | ColId indirection
{
- $$ = (Node *) makeInteger(true);
+ $$ = (Node *)makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
-publication_table_list:
- publication_table
- { $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ }
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
;
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9722,28 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12487,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15168,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17045,6 +17088,41 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r = makeRangeVar(NULL, NULL, location);
+
+ check_qualified_name(namelist, yyscanner);
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..5911824d09 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -114,10 +109,18 @@ extern List *GetAllTablesPublicationRelations(bool pubviaroot);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..8220c72469 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -487,7 +488,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..15aacf7165 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ Node *object; /* publication object could be:
+ * RangeVar - table object
+ * String - tablename or schemaname */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 402a6617a9..bfc6909a43 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v32-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v32-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 790707386ddc8cd07869152b7546fbc5c4a95dcf Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v32 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 +++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 192 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 33 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 347 insertions(+), 51 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..93ed3344d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4126,6 +4134,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4213,6 +4309,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10619,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18854,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..953e1f52cf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6407,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6443,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6453,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6479,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..7a55ad3fb6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,27 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bfc6909a43..80510a9e1a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v32-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v32-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 8539fc44d8b6218bc00c30dc7dc05bd52bd325e0 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v32 3/5] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 483 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 229 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
6 files changed, 914 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..97b447d677 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,89 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+ERROR: relation "testpub_nopk" is not part of the publication
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- 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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +177,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +353,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +399,404 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..3bd2e6bc69 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +187,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +199,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +225,201 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v32-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v32-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 9c1ea4feb3e40f5b64a9901d87ac51b940d395b7 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v32 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 88 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 ++++++++++++++++++--
3 files changed, 207 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2f0def9b19..c18a90a691 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..981e38189f 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +117,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +170,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..938237ae05 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v32-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v32-0005-Implemented-pg_publication_objects-view.patchDownload
From e7fc093932f34a2861ab722d4dc72f6fe9744e68 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v32 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c18a90a691..38293cbdba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Tue, Sep 21, 2021 at 6:05 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Sep 21, 2021 at 4:12 PM vignesh C <vignesh21@gmail.com> wrote:
(1)
In get_object_address_publication_schema(), the error message:+ errmsg("publication tables of schema \"%s\" in publication \"%s\"
does not exist",isn't grammatically correct. It should probably be:
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" do
not exist","does not exist" is used across the file. Should we keep it like that
to maintain consistency. Thoughts?When it's singular, "does not exist" is correct.
I think currently only this case exists in the publication code.
e.g.
"publication \"%s\" does not exist"
"publication relation \"%s\" in publication \"%s\" does not exist"But "publication tables" is plural, so it needs to say "do not exist"
rather than "does not exist".In the case of "if (!(*nspname))", the following line should probably
be added before returning false:*pubname = NULL;
In case of failure we return false and don't access it. I felt we
could keep it as it is. Thoughts?OK then, I might be being a bit pedantic.
(I was just thinking, strictly speaking, we probably shouldn't be
writing into the caller's pass-by-reference parameters in the case
false is returned)(5)
I'm wondering, in CheckObjSchemaNotAlreadyInPublication(), instead of
checking "checkobjtype" each loop iteration, wouldn't it be better to
just use the same for-loop in each IF block?I will be changing it to:
static void
CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
PublicationObjSpecType checkobjtype)
{
ListCell *lc;foreach(lc, rels)
{
Relation rel = (Relation) lfirst(lc);
Oid relSchemaId = RelationGetNamespace(rel);if (list_member_oid(schemaidlist, relSchemaId))
{
if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add schema \"%s\" to publication",
get_namespace_name(relSchemaId)),
errdetail("Table \"%s\" in schema \"%s\" is already part
of the publication, adding the same schema is not supported.",
RelationGetRelationName(rel),
get_namespace_name(relSchemaId)));
else if (checkobjtype == PUBLICATIONOBJ_TABLE)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add relation \"%s.%s\" to publication",
get_namespace_name(relSchemaId),
RelationGetRelationName(rel)),
errdetail("Table's schema \"%s\" is already part of the
publication.",
get_namespace_name(relSchemaId)));
}
}
}
After the change checkobjtype will be checked only once in case of error.OK.
One thing related to this code is the following:
i)
postgres=# create publication pub1 for all tables in schema sch1,
table sch1.test;
ERROR: cannot add relation "sch1.test" to publication
DETAIL: Table's schema "sch1" is already part of the publication.ii)
postgres=# create publication pub1 for table sch1.test, all tables in
schema sch1;
ERROR: cannot add relation "sch1.test" to publication
DETAIL: Table's schema "sch1" is already part of the publication.Notice that in case (ii), the same error message is used, but the
order of items to be "added" to the publication is the reverse of case
(i), and really implies the table "sch1.test" was added first, but
this is not reflected by the error message. So it seems slightly odd
to say the schema is already part of the publication, when the table
was actually listed first.
I'm wondering if this can be improved?One idea I had was the following more generic type of message, but I'm
not 100% happy with the wording:DETAIL: Schema "sch1" and one of its tables can't separately be
part of the publication.
This is common code applicable for the following scenarios:
i) create publication pub1 for all tables in schema sch1, table sch1.test;
ii) create publication pub1 for table sch1.test, all tables in schema sch1;
iii) create publication pub1 for table sch1.test;
alter publication pub1 add all tables in schema sch1;
I have changed it to make it suitable for all the cases.
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add relation \"%s.%s\" to publication",
get_namespace_name(relSchemaId),
RelationGetRelationName(rel)),
errdetail("Table's schema \"%s\" is already part of the publication or
part of the specified schema list.",
get_namespace_name(relSchemaId)));
The v32 patch attached at [1]/messages/by-id/CALDaNm1R-xbQvz4LU5OXu3KKwbWOz3uDcT_YjRU6V0R5FZDYDg@mail.gmail.com handles the above.
[1]: /messages/by-id/CALDaNm1R-xbQvz4LU5OXu3KKwbWOz3uDcT_YjRU6V0R5FZDYDg@mail.gmail.com
Regards,
Vignesh
On Wed, Sep 22, 2021 at 8:52 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Wed, Sep 22, 2021 at 3:02 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v30 patch has the fixes for the same.
Thank you for updating the patches.
Here are random comments on v30-0002 patch:
+ + if (stmt->action == DEFELEM_SET && !list_length(schemaidlist)) + { + delschemas = GetPublicationSchemas(pubform->oid); + LockSchemaList(delschemas); + }I think "list_length(schemaidlist) > 0" would be more readable.
We have refactored the patch which has removed these changes.
---
This patch introduces some new static functions to publicationcmds.c
but some have function prototypes but some don't. As far as I checked,ObjectsInPublicationToOids()
CheckObjSchemaNotAlreadyInPublication()
GetAlterPublicationDelRelations()
AlterPublicationSchemas()
CheckPublicationAlterTables()
CheckPublicationAlterSchemas()
LockSchemaList()
OpenReliIdList()
PublicationAddSchemas()
PublicationDropSchemas()are newly introduced but only four functions:
OpenReliIdList()
LockSchemaList()
PublicationAddSchemas()
PublicationDropSchemas()have function prototypes. Actually, there already are functions that
don't have their prototype in publicationcmds.c. But it seems better
to clarify what kind of functions don't need to have a prototype at
least in this file.
My thoughts are the same as that Amit had replied at [1]/messages/by-id/CAA4eK1KmccaVdKFrwKLXhewDt6rSCD2msOoYbWWWxYK5=bX5cg@mail.gmail.com.
---
ISTM we can inline the contents of three functions:
GetAlterPublicationDelRelations(), CheckPublicationAlterTables(), and
CheckPublicationAlterSchemas(). These have only one caller and ISTM
makes the readability worse. I think it's not necessary to make
functions for them.
We have refactored the patch which has removed these changes.
--- + * This is dispatcher function for AlterPublicationOptions, + * AlterPublicationSchemas and AlterPublicationTables.As this comment mentioned, AlterPublication() calls
AlterPublicationTables() and AlterPublicationSchemas() but this
function also a lot of pre-processing such as building the list and
some checks, depending on stmt->action before calling these two
functions. And those two functions also perform some operation
depending on stmt->action. So ISTM it's better to move those
pre-processing to these two functions and have AlterPublication() just
call these two functions. What do you think?
We have refactored the patch which has removed these changes.
--- +List * +GetAllSchemasPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)Since this function gets all relations in the schema publication, I
think GetAllSchemaPublicationRelations() would be better as a function
name (removing 's' before 'P').
Modified
--- + if (!IsA(node, String)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid schema name at or near"), + parser_errposition(pstate, pubobj->location));The error message should mention where the invalid schema name is at
or near. Also, In the following example, the error position in the
error message seems not to be where the invalid schemaname s.s is:
postgres(1:47707)=# create publication p for all tables in schema s.s;
ERROR: invalid schema name at or near
LINE 1: create publication p for all tables in schema s.s;
^
Modified
--- + if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE) + { + if (IsA(node, RangeVar)) + *rels = lappend(*rels, (RangeVar *) node); + else if (IsA(node, String)) + { + RangeVar *rel = makeRangeVar(NULL, strVal(node), + pubobj->location); + + *rels = lappend(*rels, rel); + } + } + else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA) + { (snip) + /* Filter out duplicates if user specifies "sch1, sch1" */ + *schemas = list_append_unique_oid(*schemas, schemaid); + }Do we need to filter out duplicates also in PUBLICATIONOBJ_TABLE case
since users can specify things like "TABLE tbl, tbl, tbl"?
Currently the handling of tables is taken care at OpenTableList:
.....
/*
* Filter out duplicates if user specifies "foo, foo".
*
* Note that this algorithm is known to not be very efficient (O(N^2))
* but given that it only works on list of tables given to us by user
* it's deemed acceptable.
*/
if (list_member_oid(relids, myrelid))
{
table_close(rel, ShareUpdateExclusiveLock);
continue;
}
.....
--- + if ((action == DEFELEM_ADD || action == DEFELEM_SET) && !superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to add or set schemas")));Why do we require the superuser privilege only for ADD and SET but not for DROP?
Amit had replied with the comments for this at [1]/messages/by-id/CAA4eK1KmccaVdKFrwKLXhewDt6rSCD2msOoYbWWWxYK5=bX5cg@mail.gmail.com.
The v32 patch attached at [2]/messages/by-id/CALDaNm1R-xbQvz4LU5OXu3KKwbWOz3uDcT_YjRU6V0R5FZDYDg@mail.gmail.com has the fixes for the above.
[1]: /messages/by-id/CAA4eK1KmccaVdKFrwKLXhewDt6rSCD2msOoYbWWWxYK5=bX5cg@mail.gmail.com
[2]: /messages/by-id/CALDaNm1R-xbQvz4LU5OXu3KKwbWOz3uDcT_YjRU6V0R5FZDYDg@mail.gmail.com
Regards,
Vignesh
On Wed, Sep 22, 2021 at 11:27 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Wednesday, September 22, 2021 11:22 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
--- + if (!IsA(node, String)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid schema name at or near"), + parser_errposition(pstate, pubobj->location));The error message should mention where the invalid schema name is at
or near. Also, In the following example, the error position in the
error message seems not to be where the invalid schemaname s.s is:postgres(1:47707)=# create publication p for all tables in schema s.s;
ERROR: invalid schema name at or near
LINE 1: create publication p for all tables in schema s.s;
^I noticed this, too. And I think it could be fixed by the following change, thoughts?
I fixed it by updating the location at pubobj_expr
Besides, about this change in tab-complete.c:
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "SCHEMA")) + COMPLETE_WITH_QUERY(Query_for_list_of_schemas + " UNION SELECT 'CURRENT_SCHEMA'");It should be "ALL TABLES IN SCHEMA" not "SCHEMA" at the first line, right?
Modified.
The v32 patch attached at [1]/messages/by-id/CALDaNm1R-xbQvz4LU5OXu3KKwbWOz3uDcT_YjRU6V0R5FZDYDg@mail.gmail.com handles the above.
[1]: /messages/by-id/CALDaNm1R-xbQvz4LU5OXu3KKwbWOz3uDcT_YjRU6V0R5FZDYDg@mail.gmail.com
Regards.
Vignesh
On Wed, Sep 22, 2021 at 11:31 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Sep 21, 2021 at 11:39 PM vignesh C <vignesh21@gmail.com> wrote:
On Tue, Sep 21, 2021 at 9:03 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Sep 17, 2021 at 10:09 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v29 patch has the fixes for the same.
Some minor comments on the v29-0002 patch:
(1)
In get_object_address_publication_schema(), the error message:+ errmsg("publication tables of schema \"%s\" in publication \"%s\"
does not exist",isn't grammatically correct. It should probably be:
+ errmsg("publication tables of schema \"%s\" in publication \"%s\" do
not exist",Modified
I still see the old message in v30. But I have a different suggestion
for this message. How about changing it to: "publication schema \"%s\"
in publication \"%s\" does not exist"? This will make it similar to
other messages and I don't see the need here to add 'tables' as we
have it in grammar.
Modified
The v32 patch attached at [1]/messages/by-id/CALDaNm1R-xbQvz4LU5OXu3KKwbWOz3uDcT_YjRU6V0R5FZDYDg@mail.gmail.com handles the above.
[1]: /messages/by-id/CALDaNm1R-xbQvz4LU5OXu3KKwbWOz3uDcT_YjRU6V0R5FZDYDg@mail.gmail.com
Regards,
Vignesh
On Thu, Sep 23, 2021 at 12:22 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
From Thurs, Sep 23, 2021 12:09 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 22, 2021 at 5:03 PM Hou Zhijie <houzj.fnst@fujitsu.com> wrote:
Personally, I think we'd better move the code about changing publication's
tablelist into AlterPublicationTables and the code about changingpublication's
schemalist into AlterPublicationSchemas. It's similar to what the
v29-patchset
did, the difference is the SET action, I suggest we drop all the tables in
function AlterPublicationTables when user only set schemas and drop all the
schema in AlterPublicationSchemas when user only set tables. In thisapproach,
we can keep schema and relation code separate and don't need to worry
about the locking order.Attach a top-up patch which refactor the code like above.
Good suggestion. I think it would still be better if we can move the
checks related to superuser and puballtables into a separate function
that gets called before taking a lock on publication.I agreed.
I noticed v30-0001 has been committed with some minor changes, and the V30-0002
patchset need to be rebased accordingly. Attach a rebased version patch set to
make cfbot happy. Also Attach the two top-up patches which refactor the code as
suggested. (top-up patch 1 is to keep schema and table code separate, top-up
patch 2 is to move some cheap check into a function and invoke it before
locking.)
Thanks for the patches, the changes simplifies alterpublications code
and handles the drop object in a better way. I have merged it to 0001
patch at [1]/messages/by-id/CALDaNm1R-xbQvz4LU5OXu3KKwbWOz3uDcT_YjRU6V0R5FZDYDg@mail.gmail.com.
[1]: /messages/by-id/CALDaNm1R-xbQvz4LU5OXu3KKwbWOz3uDcT_YjRU6V0R5FZDYDg@mail.gmail.com
Regards,
Vignesh
On Thu, Sep 23, 2021 at 1:56 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 22, 2021 at 8:52 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Wed, Sep 22, 2021 at 3:02 AM vignesh C <vignesh21@gmail.com> wrote:
---
This patch introduces some new static functions to publicationcmds.c
but some have function prototypes but some don't. As far as I checked,ObjectsInPublicationToOids()
CheckObjSchemaNotAlreadyInPublication()
GetAlterPublicationDelRelations()
AlterPublicationSchemas()
CheckPublicationAlterTables()
CheckPublicationAlterSchemas()
LockSchemaList()
OpenReliIdList()
PublicationAddSchemas()
PublicationDropSchemas()are newly introduced but only four functions:
OpenReliIdList()
LockSchemaList()
PublicationAddSchemas()
PublicationDropSchemas()have function prototypes. Actually, there already are functions that
don't have their prototype in publicationcmds.c. But it seems better
to clarify what kind of functions don't need to have a prototype at
least in this file.I think if the function is defined after its use then we declare it at
the top. Do you prefer to declare all static functions to allow ease
of usage? Do you have something else in mind?
I prefer to declare all static functions since if we have a function
prototype we don't need to include the change about the function in
future (unrelated) commits that might add a new function which uses
the function and is defined before their declarations. But it seems to
me that the policy varies per file. For instance, all functions in
vacuumlazy.c have their function prototype but functions in
publicationcmds.c seems not. I'm not going to insist on that so please
ignore this comment.
--- + if ((action == DEFELEM_ADD || action == DEFELEM_SET) && !superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to add or set schemas")));Why do we require the superuser privilege only for ADD and SET but not for DROP?
For Add/Set of for all tables of Schema is similar to all tables
publication requirement. For Drop, I don't think it is mandatory to
allow only to superuser. The same applies to Alter Publication ...
Drop table case where you don't need to be table owner whereas, for
Add, you need to be. We had a discussion on these points in this
thread. See [1] and some emails prior to it.
Thank you for sharing the link. That makes sense.
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Thu, Sep 23, 2021 at 12:32 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Thur, Sep 23, 2021 11:06 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Wed, Sep 22, 2021 at 9:33 PM houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com> wrote:
How do you suggest changing it?
Personally, I think we'd better move the code about changing
publication's tablelist into AlterPublicationTables and the code about
changing publication's schemalist into AlterPublicationSchemas. It's
similar to what the v29-patchset did, the difference is the SET
action, I suggest we drop all the tables in function
AlterPublicationTables when user only set schemas and drop all the
schema in AlterPublicationSchemas when user only set tables. In this
approach, we can keep schema and relation code separate and don't need toworry about the locking order.
Attach a top-up patch which refactor the code like above.
Thoughts ?Sounds like a good idea.
Is it possible to incorporate the existing
CheckPublicationAlterTables() and CheckPublicationAlterSchemas() functions
into your suggested update?
I think it might tidy up the error-checking a bit.I agreed we can put the check about ALL TABLE and superuser into a function
like what the v30-patchset did. But I have some hesitations about the code
related to CheckObjSchemaNotAlreadyInPublication(). Currently, we need to open
and lock the table before invoking the CheckObjxxx function, ISTM we'd better
open the table in function AlterPublicationTables. Maybe we can wait for the
author's(Vignesh) opinion.
I felt keeping the code related to
CheckObjSchemaNotAlreadyInPublication as it is in
AlterPublicationTables and AlterPublicationSchemas is better.
Regards,
Vignesh
On Fri, Sep 24, 2021 at 11:46 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v33 patch has the preprocess_pubobj_list review comment fix
suggested by Alvaro at [1]/messages/by-id/202109241325.eag5g6mpvoup@alvherre.pgsql. The
v33-0006-Alternate-grammar-for-ALL-TABLES-IN-SCHEMA.patch patch has
the grammar changes as suggested by Alvaro at [1]/messages/by-id/202109241325.eag5g6mpvoup@alvherre.pgsql. If we agree this is
better, I will merge this into the 0001 patch.
[1]: /messages/by-id/202109241325.eag5g6mpvoup@alvherre.pgsql
Regards,
Vignesh
Attachments:
v33-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v33-0001-Added-schema-level-support-for-publication.patchDownload
From 4ab8be0e2b4e7d8183e6b74d0f37778e6a489c50 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 23 Sep 2021 13:40:27 +0800
Subject: [PATCH v33 1/6] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 ++++++
src/backend/catalog/pg_publication.c | 296 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 468 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 4 +-
src/backend/nodes/copyfuncs.c | 20 +-
src/backend/nodes/equalfuncs.c | 15 +-
src/backend/parser/gram.y | 266 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 15 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 36 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1225 insertions(+), 173 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..04e785b192 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -152,7 +177,7 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -162,14 +187,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,84 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -328,6 +431,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -366,7 +536,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -428,6 +598,100 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -557,10 +821,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..c59cd5ad49 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,90 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ *rels = lappend(*rels, pubobj->object);
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ Oid schemaid;
+ char *schemaname;
+
+ schemaname = strVal(pubobj->object);
+ if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+ }
+ else
+ schemaid = get_namespace_oid(schemaname, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +244,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,17 +315,36 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- else if (stmt->for_all_tables)
+
+ if (schemaidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ Assert(list_length(schemaidlist) > 0);
+
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
@@ -318,13 +431,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +480,32 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +514,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +526,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +537,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +559,102 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in given publication and throws
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /* Check that user is allowed to manipulate the publication tables in schema */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +684,22 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /* Lock the publication so nobody else can do anything with it. */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -538,7 +763,7 @@ RemovePublicationById(Oid pubid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication %u", pubid);
- pubform = (Form_pg_publication)GETSTRUCT(tup);
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate relcache so that publication info is rebuilt. */
if (pubform->puballtables)
@@ -552,9 +777,84 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to prevent concurrent schema deletion. No need to unlock the
+ * schemas, the locks will be released at the end of the command.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +868,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +892,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +925,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +946,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +965,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +973,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +985,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1025,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1047,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..f91b9963c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..ade93023b8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4817,7 +4817,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4830,9 +4830,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4958,12 +4958,14 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_NODE_FIELD(object);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -5887,8 +5889,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..1dcd63d64f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2302,7 +2302,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2314,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3134,9 +3134,10 @@ _equalBitString(const BitString *a, const BitString *b)
}
static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
{
- COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(object);
return true;
}
@@ -3894,8 +3895,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..ed6a4ffd9b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list (List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -554,6 +560,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
+%type <publicationobjectspec> pubobj_expr
+%type <node> pubobj_name
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,69 +9600,120 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [[, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
+pubobj_expr:
+ pubobj_name
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = $1;
+ $$->location = @1;
+ }
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)$1;
+ $$->location = @1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->object = (Node *)makeString("CURRENT_SCHEMA");
+ $$->location = @1;
+ }
;
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * This can be either a schema or relation name. For relations, the inheritance
+ * will be implicit.
+ */
+pubobj_name:
+ ColId
{
- $$ = (Node *) $3;
+ $$ = (Node *)makeString($1);
}
- | FOR ALL TABLES
+ | ColId indirection
{
- $$ = (Node *) makeInteger(true);
+ $$ = (Node *)makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
-publication_table_list:
- publication_table
- { $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec: TABLE pubobj_expr
+ {
+ $$ = $2;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ }
+ | ALL TABLES IN_P SCHEMA pubobj_expr
+ {
+ $$ = $5;
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ }
+ | pubobj_expr
+ {
+ $$ = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
;
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+pub_obj_list: PublicationObjSpec
+ { $$ = list_make1($1); }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9725,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12493,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15174,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17045,6 +17094,41 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r = makeRangeVar(NULL, NULL, location);
+
+ check_qualified_name(namelist, yyscanner);
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17195,6 +17279,52 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType
+ * type.
+ */
+static void
+preprocess_pubobj_list (List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ Node *node;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+ node = (Node *) pubobj->object;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE && IsA(node, String))
+ pubobj->object = (Node *)makeRangeVar(NULL, strVal(node),
+ pubobj->location);
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA &&
+ !IsA(node, String))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..5911824d09 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -114,10 +109,18 @@ extern List *GetAllTablesPublicationRelations(bool pubviaroot);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..8220c72469 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -487,7 +488,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..15aacf7165 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ Node *object; /* publication object could be:
+ * RangeVar - table object
+ * String - tablename or schemaname */
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 402a6617a9..bfc6909a43 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v33-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v33-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From b65baee45c2421693d760907121235b5d0ff5f53 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v33 2/6] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 +++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 +++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 192 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 33 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 347 insertions(+), 51 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..93ed3344d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4126,6 +4134,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4213,6 +4309,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10619,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18854,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..953e1f52cf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5061,9 +5085,66 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ return true;
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6291,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6342,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6407,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6443,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6453,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6479,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..7a55ad3fb6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,27 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bfc6909a43..80510a9e1a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v33-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v33-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 196405f201c43c4e0f5c7c81144bf15f930080a2 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v33 3/6] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 483 +++++++++++++++++-
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 229 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
6 files changed, 914 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..97b447d677 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,89 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+ERROR: relation "testpub_nopk" is not part of the publication
+\dRp+ testpub_forschema
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test"
+
+-- 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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +177,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +353,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +399,404 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..3bd2e6bc69 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- should be able to add table to schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to drop table from schema publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +187,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +199,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +225,201 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v33-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v33-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From d39c5e98bba80b2bfcdb306a726e624f2d019677 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v33 4/6] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 88 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 ++++++++++++++++++--
3 files changed, 207 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2f0def9b19..c18a90a691 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..981e38189f 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +117,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +170,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..938237ae05 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v33-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v33-0005-Implemented-pg_publication_objects-view.patchDownload
From 453bbd0070688f56c72006a4067e9f89fb16d138 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v33 5/6] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c18a90a691..38293cbdba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
v33-0006-Alternate-grammar-for-ALL-TABLES-IN-SCHEMA.patchtext/x-patch; charset=US-ASCII; name=v33-0006-Alternate-grammar-for-ALL-TABLES-IN-SCHEMA.patchDownload
From 2e968f7a483fa8f535ffaa6ada37735aefad363f Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 27 Sep 2021 09:50:29 +0530
Subject: [PATCH v33 6/6] Alternate grammar for "ALL TABLES IN SCHEMA"
Alternate grammar for "ALL TABLES IN SCHEMA"
---
src/backend/commands/publicationcmds.c | 8 +--
src/backend/nodes/copyfuncs.c | 3 +-
src/backend/nodes/equalfuncs.c | 3 +-
src/backend/parser/gram.y | 90 ++++++++++++--------------
src/include/nodes/parsenodes.h | 5 +-
5 files changed, 52 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index c59cd5ad49..5094b4b607 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -161,14 +161,12 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
{
pubobj = (PublicationObjSpec *) lfirst(cell);
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
- *rels = lappend(*rels, pubobj->object);
+ *rels = lappend(*rels, pubobj->rangevar);
else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
{
Oid schemaid;
- char *schemaname;
- schemaname = strVal(pubobj->object);
- if (strcmp(schemaname, "CURRENT_SCHEMA") == 0)
+ if (strcmp(pubobj->name, "CURRENT_SCHEMA") == 0)
{
List *search_path;
@@ -182,7 +180,7 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
list_free(search_path);
}
else
- schemaid = get_namespace_oid(schemaname, false);
+ schemaid = get_namespace_oid(pubobj->name, false);
/* Filter out duplicates if user specifies "sch1, sch1" */
*schemas = list_append_unique_oid(*schemas, schemaid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ade93023b8..553cd834e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4964,7 +4964,8 @@ _copyPublicationObject(const PublicationObjSpec *from)
PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
COPY_SCALAR_FIELD(pubobjtype);
- COPY_NODE_FIELD(object);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
COPY_LOCATION_FIELD(location);
return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1dcd63d64f..054b2d94e5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -3137,7 +3137,8 @@ static bool
_equalPublicationObject(const PublicationObjSpec *a,
const PublicationObjSpec *b)
{
- COMPARE_NODE_FIELD(object);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ed6a4ffd9b..55eb74eaf3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -561,8 +561,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> pubobj_expr
-%type <node> pubobj_name
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9640,59 +9638,57 @@ CreatePublicationStmt:
}
;
-pubobj_expr:
- pubobj_name
+/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
+PublicationObjSpec:
+ TABLE relation_expr
{
$$ = makeNode(PublicationObjSpec);
- $$->object = $1;
- $$->location = @1;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | extended_relation_expr
+ | ALL TABLES IN_P SCHEMA ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
+ }
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = "CURRENT_SCHEMA";
+ $$->location = @5;
+ }
+ | ColId
{
$$ = makeNode(PublicationObjSpec);
- $$->object = (Node *)$1;
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
$$->location = @1;
}
- | CURRENT_SCHEMA
+ | ColId indirection
{
$$ = makeNode(PublicationObjSpec);
- $$->object = (Node *)makeString("CURRENT_SCHEMA");
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
$$->location = @1;
}
- ;
-
-/*
- * This can be either a schema or relation name. For relations, the inheritance
- * will be implicit.
- */
-pubobj_name:
- ColId
+ | CURRENT_SCHEMA
{
- $$ = (Node *)makeString($1);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->name = "CURRENT_SCHEMA";
+ $$->location = @1;
}
- | ColId indirection
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
{
- $$ = (Node *)makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
}
- ;
-
-/* FOR TABLE and FOR ALL TABLES IN SCHEMA specifications */
-PublicationObjSpec: TABLE pubobj_expr
- {
- $$ = $2;
- $$->pubobjtype = PUBLICATIONOBJ_TABLE;
- }
- | ALL TABLES IN_P SCHEMA pubobj_expr
- {
- $$ = $5;
- $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
- }
- | pubobj_expr
- {
- $$ = $1;
- $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- }
- ;
+ ;
pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
@@ -17303,19 +17299,19 @@ preprocess_pubobj_list (List *pubobjspec_list, core_yyscan_t yyscanner)
foreach(cell, pubobjspec_list)
{
- Node *node;
-
pubobj = (PublicationObjSpec *) lfirst(cell);
- node = (Node *) pubobj->object;
if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
pubobj->pubobjtype = prevobjtype;
- if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE && IsA(node, String))
- pubobj->object = (Node *)makeRangeVar(NULL, strVal(node),
- pubobj->location);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE && pubobj->name)
+ {
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA &&
- !IsA(node, String))
+ !pubobj->name)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid schema name at or near"),
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 15aacf7165..37702a4e7a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -367,9 +367,8 @@ typedef struct PublicationObjSpec
{
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
- Node *object; /* publication object could be:
- * RangeVar - table object
- * String - tablename or schemaname */
+ char *name;
+ RangeVar *rangevar;
int location; /* token location, or -1 if unknown */
} PublicationObjSpec;
--
2.30.2
On Monday, September 27, 2021 12:32 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Sep 24, 2021 at 11:46 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v33 patch has the preprocess_pubobj_list review comment fix
suggested by Alvaro at [1]. The
v33-0006-Alternate-grammar-for-ALL-TABLES-IN-SCHEMA.patch patch has
the grammar changes as suggested by Alvaro at [1]. If we agree this is better, I
will merge this into the 0001 patch.
[1] - /messages/by-id/202109241325.eag5g6mpvoup@alvherre.pgsql
Hi,
The grammar change basically looks good to me. Only one suggestion is that it
will be better to add some more comments in gram.y to describe the rule
PublicationObjSpec. Because it's a new style syntax in postgresql, people might
wonder how the code work and why we choose this design when they first time
see this rule in gram.y.
Maybe something like the following:
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication object with and without keyword prefix.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expression in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
PublicationObjSpec:
TABLE relation_expr
...
My words might not be good, but I think it will be better to add some comments
to explain a bit about the code in gram.y. Thoughts ?
Best regards,
Hou zj
On Mon, Sep 27, 2021 at 2:32 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v33 patch has the preprocess_pubobj_list review comment fix
suggested by Alvaro at [1].
A minor point I noticed in the v33-0002 patch, in the code added to
the listSchemas() function of src/bin/psql/describe.c, shouldn't it
"return false" (not true) if PSQLexec() fails?
Also, since the PQExpBufferData buf is re-used in the added code, it's
handling is a little inconsistent to similar existing code.
See below for suggested update.
Regards,
Greg Nancarrow
Fujitsu Australia
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 953e1f52cf..1d28809050 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5077,9 +5077,11 @@ listSchemas(const char *pattern, bool verbose,
bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
@@ -5100,7 +5102,10 @@ listSchemas(const char *pattern, bool verbose,
bool showSystem)
pattern);
result = PSQLexec(buf.data);
if (!result)
- return true;
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
else
pub_schema_tuples = PQntuples(result);
@@ -5132,6 +5137,7 @@ listSchemas(const char *pattern, bool verbose,
bool showSystem)
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
/* Free the memory allocated for the footer */
On Mon, Sep 27, 2021 at 2:32 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v33 patch has the preprocess_pubobj_list review comment fix
suggested by Alvaro at [1].
In the v33-0003 patch, there's a couple of error-case tests that have
comments copied from success-case tests:
+-- should be able to add table to schema publication
...
+-- should be able to drop table from schema publication
...
These should be changed to something similar to that used for other
error-case tests, like:
+-- fail - can't add a table of the same schema to the schema publication
+-- fail - can't drop a table from the schema publication which isn't
in the publication
Also, for the following error:
ERROR: cannot add ... to publication
DETAIL: Table's schema "xxxx" is already part of the publication
or part of the specified schema list.
there needs to be a test case to test the "... or part of the
specified schema list" case.
Regards,
Greg Nancarrow
Fujitsu Australia
On Monday, September 27, 2021 1:32 PM, vignesh C <vignesh21@gmail.com> wrote:
Attached v33 patch has the preprocess_pubobj_list review comment fix
suggested by Alvaro at [1]. The
v33-0006-Alternate-grammar-for-ALL-TABLES-IN-SCHEMA.patch patch has
the grammar changes as suggested by Alvaro at [1]. If we agree this is
better, I will merge this into the 0001 patch.
[1] - /messages/by-id/202109241325.eag5g6mpvoup@alvherre.pgsql
About the schema patch, I think a schema and a table which belongs to this schema shouldn't be specified at the same time.
But what if someone uses "ALTER TABLE ... SET SCHEMA ..." after "CREATE PUBLICATION"?
For example:
create schema sch1;
create schema sch2;
create table sch2.t (a int);
create publication pub1 for all tables in schema sch1, table sch2.t; alter table sch2.t set schema sch1;
postgres=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
----------+------------+---------+---------+---------+-----------+------
----------+------------+---------+---------+---------+-----------+----
postgres | f | t | t | t | t | f
Tables:
"sch1.t"
Tables from schemas:
"sch1"
Table t has been output twice.
I think this should not be supported, should we do something for this scenario?
Regards
Tang
On Mon, Sep 27, 2021 at 12:15 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Monday, September 27, 2021 12:32 PM vignesh C <vignesh21@gmail.com> wrote:
On Fri, Sep 24, 2021 at 11:46 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v33 patch has the preprocess_pubobj_list review comment fix
suggested by Alvaro at [1]. The
v33-0006-Alternate-grammar-for-ALL-TABLES-IN-SCHEMA.patch patch has
the grammar changes as suggested by Alvaro at [1]. If we agree this is better, I
will merge this into the 0001 patch.
[1] - /messages/by-id/202109241325.eag5g6mpvoup@alvherre.pgsqlHi,
The grammar change basically looks good to me. Only one suggestion is that it
will be better to add some more comments in gram.y to describe the rule
PublicationObjSpec. Because it's a new style syntax in postgresql, people might
wonder how the code work and why we choose this design when they first time
see this rule in gram.y.Maybe something like the following:
+/* + * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications + * + * This rule parses publication object with and without keyword prefix. + * + * The actual type of the object without keyword prefix depends on the previous + * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list(). + * + * For the object without keyword prefix, we cannot just use relation_expr here, + * because some extended expression in relation_expr cannot be used as a + * schemaname and we cannot differentiate it. So, we extract the rules from + * relation_expr here. + */ PublicationObjSpec: TABLE relation_expr ...My words might not be good, but I think it will be better to add some comments
to explain a bit about the code in gram.y. Thoughts ?
Modified.
Attached v34 patch has the changes for the same.
Regards,
Vignesh
Attachments:
v34-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v34-0001-Added-schema-level-support-for-publication.patchDownload
From 29d8d98ef502346d1b4431fd3cb2cd291ff8c9c0 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 23 Sep 2021 13:40:27 +0800
Subject: [PATCH v34 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 ++++++
src/backend/catalog/pg_publication.c | 296 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 466 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +-
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 16 +-
src/backend/parser/gram.y | 282 ++++++++---
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 15 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 35 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1260 insertions(+), 177 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..04e785b192 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -152,7 +177,7 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -162,14 +187,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,84 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -328,6 +431,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -366,7 +536,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -428,6 +598,100 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -557,10 +821,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..5094b4b607 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,88 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ *rels = lappend(*rels, pubobj->rangevar);
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ Oid schemaid;
+
+ if (strcmp(pubobj->name, "CURRENT_SCHEMA") == 0)
+ {
+ List *search_path;
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+ }
+ else
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +242,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,17 +313,36 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+ if (relations != NIL)
{
List *rels;
- Assert(list_length(stmt->tables) > 0);
+ Assert(list_length(relations) > 0);
- rels = OpenTableList(stmt->tables);
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(puboid, rels, true, NULL);
CloseTableList(rels);
}
- else if (stmt->for_all_tables)
+
+ if (schemaidlist != NIL)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ Assert(list_length(schemaidlist) > 0);
+
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
@@ -318,13 +429,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +478,32 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +512,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +524,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +535,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +557,102 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in given publication and throws
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /* Check that user is allowed to manipulate the publication tables in schema */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +682,22 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /* Lock the publication so nobody else can do anything with it. */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -538,7 +761,7 @@ RemovePublicationById(Oid pubid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication %u", pubid);
- pubform = (Form_pg_publication)GETSTRUCT(tup);
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate relcache so that publication info is rebuilt. */
if (pubform->puballtables)
@@ -552,9 +775,84 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to prevent concurrent schema deletion. No need to unlock the
+ * schemas, the locks will be released at the end of the command.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +866,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +890,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +923,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +944,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +963,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +971,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +983,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1023,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1045,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..18da043097 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
@@ -15960,6 +15962,30 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check if setting the relation to a different schema will result in the
+ * publication having schema and same schema's table in the publication.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ foreach(lc, schemaPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+ if (list_member_oid(GetPublicationRelations(pubid, PUBLICATION_PART_ALL),
+ relid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("Altering table will result in having schema \"%s\" and same schema's table \"%s\" in the publication \"%s\" which is not supported.",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..553cd834e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4817,7 +4817,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4830,9 +4830,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4958,12 +4958,15 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..054b2d94e5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2302,7 +2302,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2314,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3134,9 +3134,11 @@ _equalBitString(const BitString *a, const BitString *b)
}
static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
{
- COMPARE_NODE_FIELD(relation);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
return true;
}
@@ -3894,8 +3896,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..65225f316d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -554,6 +560,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,69 +9598,130 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [[, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication object with and without keyword prefix.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expression in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = "CURRENT_SCHEMA";
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->name = "CURRENT_SCHEMA";
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9733,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12501,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15182,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17045,6 +17102,41 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r = makeRangeVar(NULL, NULL, location);
+
+ check_qualified_name(namelist, yyscanner);
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17195,6 +17287,52 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType
+ * type.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE && pubobj->name)
+ {
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA &&
+ !pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..5911824d09 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -114,10 +109,18 @@ extern List *GetAllTablesPublicationRelations(bool pubviaroot);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..8220c72469 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -487,7 +488,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..37702a4e7a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,25 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1835,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3656,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3673,11 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 402a6617a9..bfc6909a43 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v34-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v34-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From c074b788cd5fd51dcb55348ca90ae947cd4f835f Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v34 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 200 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 33 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 354 insertions(+), 52 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..93ed3344d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4126,6 +4134,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4213,6 +4309,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10619,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18854,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0ef..f1ccc2a9a1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5053,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6348,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6413,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6449,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6459,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6485,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..7a55ad3fb6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,27 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bfc6909a43..80510a9e1a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v34-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v34-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From a2ddcabda13c70d31275cb744f71d66a39aeeea5 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v34 3/5] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 471 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 229 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 +++++++
8 files changed, 928 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..c5a3052e52 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: Altering table will result in having schema "alter2" and same schema's table "t1" in the publication "pub1" which is not supported.
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..1af0e8b6c9 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,77 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +165,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +341,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +387,404 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN 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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+(1 row)
+
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200adcb..433388ee64 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..255a71f97c 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +187,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +199,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +225,201 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub4_forschema;
+RESET client_min_messages;
+\dRp+ testpub4_forschema
+ALTER PUBLICATION testpub4_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub4_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub4_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v34-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v34-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 533443bd1b004a3d3df64440162208d806ea62dc Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v34 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 88 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 ++++++++++++++++++--
3 files changed, 207 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2f0def9b19..c18a90a691 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..981e38189f 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +117,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +170,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..938237ae05 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v34-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v34-0005-Implemented-pg_publication_objects-view.patchDownload
From 1092342d1c3e66e6558196f76170ff90e91da0df Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v34 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c18a90a691..38293cbdba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Mon, Sep 27, 2021 at 2:46 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Mon, Sep 27, 2021 at 2:32 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v33 patch has the preprocess_pubobj_list review comment fix
suggested by Alvaro at [1].A minor point I noticed in the v33-0002 patch, in the code added to
the listSchemas() function of src/bin/psql/describe.c, shouldn't it
"return false" (not true) if PSQLexec() fails?
Also, since the PQExpBufferData buf is re-used in the added code, it's
handling is a little inconsistent to similar existing code.
See below for suggested update.
Modified
This is handled in the v34 patch attached at [1]/messages/by-id/CALDaNm2Z9TfuoCf09YGKfwy7F1NwC4iCXJGTaZS=chH6VHtadQ@mail.gmail.com.
[1]: /messages/by-id/CALDaNm2Z9TfuoCf09YGKfwy7F1NwC4iCXJGTaZS=chH6VHtadQ@mail.gmail.com
Regards,
Vignesh
On Mon, Sep 27, 2021 at 4:51 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Mon, Sep 27, 2021 at 2:32 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v33 patch has the preprocess_pubobj_list review comment fix
suggested by Alvaro at [1].In the v33-0003 patch, there's a couple of error-case tests that have
comments copied from success-case tests:+-- should be able to add table to schema publication ... +-- should be able to drop table from schema publication ...These should be changed to something similar to that used for other
error-case tests, like:+-- fail - can't add a table of the same schema to the schema publication +-- fail - can't drop a table from the schema publication which isn't in the publication
Modified
Also, for the following error:
ERROR: cannot add ... to publication
DETAIL: Table's schema "xxxx" is already part of the publication
or part of the specified schema list.there needs to be a test case to test the "... or part of the
specified schema list" case.
Added the test
This is handled in the v34 patch attached at [1]/messages/by-id/CALDaNm2Z9TfuoCf09YGKfwy7F1NwC4iCXJGTaZS=chH6VHtadQ@mail.gmail.com.
[1]: /messages/by-id/CALDaNm2Z9TfuoCf09YGKfwy7F1NwC4iCXJGTaZS=chH6VHtadQ@mail.gmail.com
Regards,
Vignesh
On Tue, Sep 28, 2021 at 4:35 PM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Monday, September 27, 2021 1:32 PM, vignesh C <vignesh21@gmail.com> wrote:
Attached v33 patch has the preprocess_pubobj_list review comment fix
suggested by Alvaro at [1]. The
v33-0006-Alternate-grammar-for-ALL-TABLES-IN-SCHEMA.patch patch has
the grammar changes as suggested by Alvaro at [1]. If we agree this is
better, I will merge this into the 0001 patch.
[1] - /messages/by-id/202109241325.eag5g6mpvoup@alvherre.pgsqlAbout the schema patch, I think a schema and a table which belongs to this schema shouldn't be specified at the same time.
But what if someone uses "ALTER TABLE ... SET SCHEMA ..." after "CREATE PUBLICATION"?For example:
create schema sch1;
create schema sch2;
create table sch2.t (a int);
create publication pub1 for all tables in schema sch1, table sch2.t; alter table sch2.t set schema sch1;postgres=# \dRp+
Publication pub1
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
----------+------------+---------+---------+---------+-----------+------
----------+------------+---------+---------+---------+-----------+----
postgres | f | t | t | t | t | f
Tables:
"sch1.t"
Tables from schemas:
"sch1"Table t has been output twice.
I think this should not be supported, should we do something for this scenario?
Yes this should not be supported, we should throw an error in this case.
This is handled in the v34 patch attached at [1]/messages/by-id/CALDaNm2Z9TfuoCf09YGKfwy7F1NwC4iCXJGTaZS=chH6VHtadQ@mail.gmail.com.
[1]: /messages/by-id/CALDaNm2Z9TfuoCf09YGKfwy7F1NwC4iCXJGTaZS=chH6VHtadQ@mail.gmail.com
Regards,
Vignesh
On Monday, Tuesday, September 28, 2021 10:49 PM, vignesh C <vignesh21@gmail.com> wrote:
Yes this should not be supported, we should throw an error in this case.
This is handled in the v34 patch attached at [1].
[1] - https://www.postgresql.org/message-
id/CALDaNm2Z9TfuoCf09YGKfwy7F1NwC4iCXJGTaZS%3DchH6VHtadQ%40mail.g
mail.com
Thanks for fixing it. I confirmed the error can be output as expected.
Here is a problem related to publish_via_partition_root option when using this
patch. With this option on, I think pg_get_publication_tables function gave an
unexcepted result and the subscriber would get dual data during table sync.
For example:
(I used pg_publication_tables view to make it looks clearer)
create schema sch1;
create table sch1.tbl1 (a int) partition by range ( a );
create table sch1.tbl1_part1 partition of sch1.tbl1 for values from (1) to (10);
create table sch1.tbl1_part2 partition of sch1.tbl1 for values from (10) to (20);
create table sch1.tbl1_part3 partition of sch1.tbl1 for values from (20) to (30);
create publication pub for all tables in schema sch1 with(publish_via_partition_root=1);
postgres=# select * from pg_publication_tables where pubname='pub';
pubname | schemaname | tablename
---------+------------+------------
pub | sch1 | tbl1_part1
pub | sch1 | tbl1_part2
pub | sch1 | tbl1_part3
pub | sch1 | tbl1
(4 rows)
It shows both the partitioned table and its leaf partitions. But the result of
FOR ALL TABLES publication couldn't show the leaf partitions.
postgres=# create publication pub_all for all tables with(publish_via_partition_root=1);
CREATE PUBLICATION
postgres=# select * from pg_publication_tables where pubname='pub_all';
pubname | schemaname | tablename
---------+------------+-----------
pub_all | sch1 | tbl1
(1 row)
How about make the following change to avoid it? I tried it and it also fixed dual
data issue during table sync.
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 04e785b192..4e8ccdabc6 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -632,7 +632,8 @@ GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
Oid relid = relForm->oid;
- if (is_publishable_class(relid, relForm))
+ if (is_publishable_class(relid, relForm) &&
+ !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
result = lappend_oid(result, relid);
}
Regards
Tang
On Tues, Sep 28, 2021 10:46 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v34 patch has the changes for the same.
Thanks for updating the patch.
Here are a few comments.
1)
+ * ALL TABLES IN SCHEMA schema [[, ...]
[[ -> [
2)
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */
The two '/' seems a bit unclear and it doesn't mention the SET case.
Maybe we can write like:
/* parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication objects */
3)
+ /*
+ * Check if setting the relation to a different schema will result in the
+ * publication having schema and same schema's table in the publication.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ foreach(lc, schemaPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+ if (list_member_oid(GetPublicationRelations(pubid, PUBLICATION_PART_ALL),
+ relid))
+ ereport(ERROR,
How about we check this case like the following ?
List *schemaPubids = GetSchemaPublications(nspOid);
List *relPubids = GetRelationPublications(RelationGetRelid(rel));
if (list_intersection(schemaPubids, relPubids))
ereport(ERROR, ...
Best regards,
Hou zj
On Wed, Sep 29, 2021 at 9:07 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Tues, Sep 28, 2021 10:46 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v34 patch has the changes for the same.
3) + /* + * Check if setting the relation to a different schema will result in the + * publication having schema and same schema's table in the publication. + */ + if (stmt->objectType == OBJECT_TABLE) + { + ListCell *lc; + List *schemaPubids = GetSchemaPublications(nspOid); + foreach(lc, schemaPubids) + { + Oid pubid = lfirst_oid(lc); + if (list_member_oid(GetPublicationRelations(pubid, PUBLICATION_PART_ALL), + relid)) + ereport(ERROR,How about we check this case like the following ?
List *schemaPubids = GetSchemaPublications(nspOid);
List *relPubids = GetRelationPublications(RelationGetRelid(rel));
if (list_intersection(schemaPubids, relPubids))
ereport(ERROR, ...
Won't this will allow changing one of the partitions for which only
partitioned table is part of the target schema? And then probably we
won't be able to provide the exact publication in the error message if
we followed the above?
--
With Regards,
Amit Kapila.
On Tue, Sep 28, 2021 at 8:15 PM vignesh C <vignesh21@gmail.com> wrote:
On Mon, Sep 27, 2021 at 12:15 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:Attached v34 patch has the changes for the same.
Few comments on v34-0001-Added-schema-level-support-for-publication
==========================================================
1.
+ * This rule parses publication object with and without keyword prefix.
I think we should write it as: "This rule parses publication objects
with and without keyword prefixes."
2.
+ * For the object without keyword prefix, we cannot just use
relation_expr here,
+ * because some extended expression in relation_expr cannot be used as a
/expression/expressions
3.
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType
+ * type.
4.
+ /*
+ * Check if setting the relation to a different schema will result in the
+ * publication having schema and same schema's table in the publication.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ foreach(lc, schemaPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+ if (list_member_oid(GetPublicationRelations(pubid, PUBLICATION_PART_ALL),
+ relid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("Altering table will result in having schema \"%s\" and
same schema's table \"%s\" in the publication \"%s\" which is not
supported.",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
Let's slightly change the comment as: "Check that setting the relation
to a different schema won't result in the publication having schema
and the same schema's table." and errdetail as: "The schema \"%s\" and
same schema's table \"%s\" cannot be part of the same publication
\"%s\"."
Maybe it is better to specify that this will disallow the partition table case.
5.
ObjectsInPublicationToOids()
{
..
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
It is better to keep an empty line between above two lines.
6.
List *schemaPubids = GetSchemaPublications(nspOid);
foreach(lc, schemaPubids)
..
Oid pubid = lfirst_oid(lc);
if (list_member_oid(GetPublicationRelations(pubid, PUBLICATION_PART_ALL),
Add an empty line between each of the above two lines.
7.
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion. No need to unlock the schemas, the locks
+ * will be released automatically at the end of alter publication command.
+ */
+ LockSchemaList(schemaidlist);
I think it is better to add a similar comment at other places where
this function is called. And we can shorten the comment atop
LockSchemaList to something like: "The schemas specified in the schema
list are locked in AccessShareLock mode in order to prevent concurrent
schema deletion."
8. In CreatePublication(), the check if (stmt->for_all_tables) can be
the first check and then in else if we can process tables and schemas.
9.
AlterPublication()
{
..
+ /* Lock the publication so nobody else can do anything with it. */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
I think it is better to say why we need this lock. So, can we change
the comment to something like: "Lock the publication so nobody else
can do anything with it. This prevents concurrent alter to add
table(s) that were already going to become part of the publication by
adding corresponding schema(s) via this command and similarly it will
prevent the concurrent addition of schema(s) for which there is any
corresponding table being added by this command."
--
With Regards,
Amit Kapila.
On Wed, Sep 29, 2021 at 3:16 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
4. + /* + * Check if setting the relation to a different schema will result in the + * publication having schema and same schema's table in the publication. + */ + if (stmt->objectType == OBJECT_TABLE) + { + ListCell *lc; + List *schemaPubids = GetSchemaPublications(nspOid); + foreach(lc, schemaPubids) + { + Oid pubid = lfirst_oid(lc); + if (list_member_oid(GetPublicationRelations(pubid, PUBLICATION_PART_ALL), + relid)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot move table \"%s\" to schema \"%s\"", + RelationGetRelationName(rel), stmt->newschema), + errdetail("Altering table will result in having schema \"%s\" and same schema's table \"%s\" in the publication \"%s\" which is not supported.", + stmt->newschema, + RelationGetRelationName(rel), + get_publication_name(pubid, false))); + } + }Let's slightly change the comment as: "Check that setting the relation
to a different schema won't result in the publication having schema
and the same schema's table." and errdetail as: "The schema \"%s\" and
same schema's table \"%s\" cannot be part of the same publication
\"%s\"."
Since this code is in AlterTableNamespace() and the relation being
checked may or may not be part of a publication, I'd use "a
publication" instead of "the publication" in the comment.
Also, I'd say that we're doing the check because the mentioned
combination is not supported.
i.e. "Check that setting the relation to a different schema won't
result in a publication having both a schema and the same schema's
table, as this is not supported."
Regards,
Greg Nancarrow
Fujitsu Australia
On Wed, Sep 29, 2021 12:34 PM Amit Kapila <amit.kapila16@gmail.com>
On Wed, Sep 29, 2021 at 9:07 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:On Tues, Sep 28, 2021 10:46 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v34 patch has the changes for the same.
3) + /* + * Check if setting the relation to a different schema will result in the + * publication having schema and same schema's table in thepublication.
+ */ + if (stmt->objectType == OBJECT_TABLE) + { + ListCell *lc; + List *schemaPubids =GetSchemaPublications(nspOid);
+ foreach(lc, schemaPubids) + { + Oid pubid = lfirst_oid(lc); + if (list_member_oid(GetPublicationRelations(pubid,PUBLICATION_PART_ALL),
+ relid)) + ereport(ERROR,How about we check this case like the following ?
List *schemaPubids = GetSchemaPublications(nspOid);
List *relPubids = GetRelationPublications(RelationGetRelid(rel));
if (list_intersection(schemaPubids, relPubids))
ereport(ERROR, ...Won't this will allow changing one of the partitions for which only partitioned
table is part of the target schema?
I think it still disallow changing partition's schema to the published one.
I tested with the following SQLs.
-----
create schema sch1;
create schema sch2;
create schema sch3;
create table sch1.tbl1 (a int) partition by range ( a );
create table sch2.tbl1_part1 partition of sch1.tbl1 for values from (1) to (101);
create table sch3.tbl1_part2 partition of sch1.tbl1 for values from (101) to (200);
create publication pub for ALL TABLES IN schema sch1, TABLE sch2.tbl1_part1;
alter table sch2.tbl1_part1 set schema sch1;
---* It will report an error here *
-----
Did I miss something ?
Best regards,
Hou zj
On Wed, Sep 29, 2021 at 11:59 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Wed, Sep 29, 2021 12:34 PM Amit Kapila <amit.kapila16@gmail.com>
On Wed, Sep 29, 2021 at 9:07 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:On Tues, Sep 28, 2021 10:46 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v34 patch has the changes for the same.
3) + /* + * Check if setting the relation to a different schema will result in the + * publication having schema and same schema's table in thepublication.
+ */ + if (stmt->objectType == OBJECT_TABLE) + { + ListCell *lc; + List *schemaPubids =GetSchemaPublications(nspOid);
+ foreach(lc, schemaPubids) + { + Oid pubid = lfirst_oid(lc); + if (list_member_oid(GetPublicationRelations(pubid,PUBLICATION_PART_ALL),
+ relid)) + ereport(ERROR,How about we check this case like the following ?
List *schemaPubids = GetSchemaPublications(nspOid);
List *relPubids = GetRelationPublications(RelationGetRelid(rel));
if (list_intersection(schemaPubids, relPubids))
ereport(ERROR, ...Won't this will allow changing one of the partitions for which only partitioned
table is part of the target schema?I think it still disallow changing partition's schema to the published one.
I tested with the following SQLs.
-----
create schema sch1;
create schema sch2;
create schema sch3;create table sch1.tbl1 (a int) partition by range ( a );
create table sch2.tbl1_part1 partition of sch1.tbl1 for values from (1) to (101);
create table sch3.tbl1_part2 partition of sch1.tbl1 for values from (101) to (200);
create publication pub for ALL TABLES IN schema sch1, TABLE sch2.tbl1_part1;
alter table sch2.tbl1_part1 set schema sch1;
---* It will report an error here *
-----
Use all steps before "create publication" and then try below. These
will give an error with the patch proposed but if I change it to what
you are proposing then it won't give an error.
create publication pub for ALL TABLES IN schema sch2, Table sch1.tbl1;
alter table sch3.tbl1_part2 set schema sch2;
But now again thinking about it, I am not sure if we really want to
give error in this case. What do you think? Also, if we use
list_intersection trick, then how will we tell the publication due to
which this problem has occurred, or do you think we should leave that
as an exercise for the user?
--
With Regards,
Amit Kapila.
On Wed, Sep 29, 2021 5:14 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 29, 2021 at 11:59 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:On Wed, Sep 29, 2021 12:34 PM Amit Kapila <amit.kapila16@gmail.com>
On Wed, Sep 29, 2021 at 9:07 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:On Tues, Sep 28, 2021 10:46 PM vignesh C <vignesh21@gmail.com>
wrote:
Attached v34 patch has the changes for the same.
How about we check this case like the following ?
List *schemaPubids = GetSchemaPublications(nspOid);
List *relPubids = GetRelationPublications(RelationGetRelid(rel));
if (list_intersection(schemaPubids, relPubids))
ereport(ERROR, ...Won't this will allow changing one of the partitions for which only
partitioned table is part of the target schema?I think it still disallow changing partition's schema to the published one.
I tested with the following SQLs.
-----
create schema sch1;
create schema sch2;
create schema sch3;create table sch1.tbl1 (a int) partition by range ( a ); create table
sch2.tbl1_part1 partition of sch1.tbl1 for values from (1) to (101);
create table sch3.tbl1_part2 partition of sch1.tbl1 for values from
(101) to (200); create publication pub for ALL TABLES IN schema sch1,
TABLE sch2.tbl1_part1; alter table sch2.tbl1_part1 set schema sch1;
---* It will report an error here *
-----Use all steps before "create publication" and then try below. These will give an
error with the patch proposed but if I change it to what you are proposing then
it won't give an error.
create publication pub for ALL TABLES IN schema sch2, Table sch1.tbl1; alter
table sch3.tbl1_part2 set schema sch2;But now again thinking about it, I am not sure if we really want to give error in
this case. What do you think?
Personally, I think we can allow the above case.
Because if user specify the partitioned table in the publication like above,
they cannot drop the partition separately. And the partitioned table is the
actual one in pg_publication_rel. So, I think allowing this case seems won't
make people feel confused.
Besides, in the current patch, we have allowed similar case in CREATE/ALTER
PUBLICATION cases. In this SQL: "create publication pub for ALL TABLES IN
schema sch2, Table sch1.tbl1;", one of the partitions ' sch2.tbl1_part1' is
from schema 'sch2' which is published. It might be better to make the behavior
consistent.
Also, if we use list_intersection trick, then how will
we tell the publication due to which this problem has occurred, or do you think
we should leave that as an exercise for the user?
I thought list_intersection will return a puboids list in which the puboid exists in both input list.
We can choose the first puboid and output it in the error message which seems the same as
the current patch.
But I noticed list_intersection doesn't support T_OidList, so we might need to search the puboid
Manually. Like:
foreach(cell, relPubids)
{
if (list_member_oid(schemaPubids, lfirst_oid(cell)))
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move table \"%s\" to schema \"%s\"",
RelationGetRelationName(rel), stmt->newschema),
errdetail("Altering table will result in having schema \"%s\" and same schema's table \"%s\" in the publication \"%s\" which is not supported.",
stmt->newschema,
RelationGetRelationName(rel),
get_publication_name(lfirst_oid(cell), false)));
}
Best regards,
Hou zj
On Wed, Sep 29, 2021 at 5:48 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Wed, Sep 29, 2021 5:14 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Sep 29, 2021 at 11:59 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:On Wed, Sep 29, 2021 12:34 PM Amit Kapila <amit.kapila16@gmail.com>
On Wed, Sep 29, 2021 at 9:07 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:On Tues, Sep 28, 2021 10:46 PM vignesh C <vignesh21@gmail.com>
wrote:
Attached v34 patch has the changes for the same.
How about we check this case like the following ?
List *schemaPubids = GetSchemaPublications(nspOid);
List *relPubids = GetRelationPublications(RelationGetRelid(rel));
if (list_intersection(schemaPubids, relPubids))
ereport(ERROR, ...Won't this will allow changing one of the partitions for which only
partitioned table is part of the target schema?I think it still disallow changing partition's schema to the published one.
I tested with the following SQLs.
-----
create schema sch1;
create schema sch2;
create schema sch3;create table sch1.tbl1 (a int) partition by range ( a ); create table
sch2.tbl1_part1 partition of sch1.tbl1 for values from (1) to (101);
create table sch3.tbl1_part2 partition of sch1.tbl1 for values from
(101) to (200); create publication pub for ALL TABLES IN schema sch1,
TABLE sch2.tbl1_part1; alter table sch2.tbl1_part1 set schema sch1;
---* It will report an error here *
-----Use all steps before "create publication" and then try below. These will give an
error with the patch proposed but if I change it to what you are proposing then
it won't give an error.
create publication pub for ALL TABLES IN schema sch2, Table sch1.tbl1; alter
table sch3.tbl1_part2 set schema sch2;But now again thinking about it, I am not sure if we really want to give error in
this case. What do you think?Personally, I think we can allow the above case.
Because if user specify the partitioned table in the publication like above,
they cannot drop the partition separately. And the partitioned table is the
actual one in pg_publication_rel. So, I think allowing this case seems won't
make people feel confused.
Yeah, I also thought on similar lines. So, let's allow this case.
Also, if we use list_intersection trick, then how will
we tell the publication due to which this problem has occurred, or do you think
we should leave that as an exercise for the user?I thought list_intersection will return a puboids list in which the puboid exists in both input list.
We can choose the first puboid and output it in the error message which seems the same as
the current patch.But I noticed list_intersection doesn't support T_OidList, so we might need to search the puboid
Manually. Like:foreach(cell, relPubids)
{
if (list_member_oid(schemaPubids, lfirst_oid(cell)))
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move table \"%s\" to schema \"%s\"",
RelationGetRelationName(rel), stmt->newschema),
errdetail("Altering table will result in having schema \"%s\" and same schema's table \"%s\" in the publication \"%s\" which is not supported.",
stmt->newschema,
RelationGetRelationName(rel),
get_publication_name(lfirst_oid(cell), false)));
}
Looks good to me.
--
With Regards,
Amit Kapila.
On Wed, Sep 29, 2021 at 8:47 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Monday, Tuesday, September 28, 2021 10:49 PM, vignesh C <vignesh21@gmail.com> wrote:
Yes this should not be supported, we should throw an error in this case.
This is handled in the v34 patch attached at [1].
[1] - https://www.postgresql.org/message-
id/CALDaNm2Z9TfuoCf09YGKfwy7F1NwC4iCXJGTaZS%3DchH6VHtadQ%40mail.g
mail.comThanks for fixing it. I confirmed the error can be output as expected.
Here is a problem related to publish_via_partition_root option when using this
patch. With this option on, I think pg_get_publication_tables function gave an
unexcepted result and the subscriber would get dual data during table sync.For example:
(I used pg_publication_tables view to make it looks clearer)create schema sch1;
create table sch1.tbl1 (a int) partition by range ( a );
create table sch1.tbl1_part1 partition of sch1.tbl1 for values from (1) to (10);
create table sch1.tbl1_part2 partition of sch1.tbl1 for values from (10) to (20);
create table sch1.tbl1_part3 partition of sch1.tbl1 for values from (20) to (30);
create publication pub for all tables in schema sch1 with(publish_via_partition_root=1);postgres=# select * from pg_publication_tables where pubname='pub';
pubname | schemaname | tablename
---------+------------+------------
pub | sch1 | tbl1_part1
pub | sch1 | tbl1_part2
pub | sch1 | tbl1_part3
pub | sch1 | tbl1
(4 rows)It shows both the partitioned table and its leaf partitions. But the result of
FOR ALL TABLES publication couldn't show the leaf partitions.postgres=# create publication pub_all for all tables with(publish_via_partition_root=1);
CREATE PUBLICATION
postgres=# select * from pg_publication_tables where pubname='pub_all';
pubname | schemaname | tablename
---------+------------+-----------
pub_all | sch1 | tbl1
(1 row)How about make the following change to avoid it? I tried it and it also fixed dual
data issue during table sync.diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index 04e785b192..4e8ccdabc6 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -632,7 +632,8 @@ GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt) Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple); Oid relid = relForm->oid;- if (is_publishable_class(relid, relForm)) + if (is_publishable_class(relid, relForm) && + !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT)) result = lappend_oid(result, relid); }
The suggested change works, I have modified it in the attached patch.
Regards,
Vignesh
Attachments:
v35-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v35-0001-Added-schema-level-support-for-publication.patchDownload
From 4d0fb9c9f87436cd8bfb5f326a99773208ce14f2 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 23 Sep 2021 13:40:27 +0800
Subject: [PATCH v35 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 ++++++
src/backend/catalog/pg_publication.c | 297 +++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 506 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 31 +-
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 16 +-
src/backend/parser/gram.y | 297 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 15 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1316 insertions(+), 184 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..4e8ccdabc6 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -152,7 +177,7 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -162,14 +187,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,84 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -328,6 +431,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -366,7 +536,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -428,6 +598,101 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm) &&
+ !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -557,10 +822,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..2f4d0b1544 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,95 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ Assert(0);
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +249,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +320,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * The schemas specified in the schema list are locked in
+ * AccessShareLock mode in order to prevent concurrent schema
+ * deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +440,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +489,32 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +523,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +535,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +546,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +568,110 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * The schemas specified in the schema list are locked in AccessShareLock
+ * mode in order to prevent concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * The schemas specified in the schema list are locked in
+ * AccessShareLock mode in order to prevent concurrent schema
+ * deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in given publication and throws
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +701,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -538,7 +787,7 @@ RemovePublicationById(Oid pubid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication %u", pubid);
- pubform = (Form_pg_publication)GETSTRUCT(tup);
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate relcache so that publication info is rebuilt. */
if (pubform->puballtables)
@@ -552,9 +801,84 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to prevent concurrent schema deletion. No need to unlock the
+ * schemas, the locks will be released at the end of the command.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +892,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +916,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +949,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +970,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +989,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +997,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1009,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1049,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1071,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ff97b618e6..445cae945b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
@@ -15960,6 +15962,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..553cd834e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4817,7 +4817,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4830,9 +4830,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4958,12 +4958,15 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..054b2d94e5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2302,7 +2302,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2314,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3134,9 +3134,11 @@ _equalBitString(const BitString *a, const BitString *b)
}
static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
{
- COMPARE_NODE_FIELD(relation);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
return true;
}
@@ -3894,8 +3896,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..4f11ef5cbc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -554,6 +560,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17045,6 +17100,41 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r = makeRangeVar(NULL, NULL, location);
+
+ check_qualified_name(namelist, yyscanner);
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17195,6 +17285,69 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name was specified as CURRENT_SCHEMA */
+ if (!pubobj->name && !pubobj->rangevar)
+ pubobj->rangevar = makeRangeVar(NULL, "CURRENT_SCHEMA",
+ pubobj->location);
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ /*
+ * Schema name was specified as CURRENT_SCHEMA, set pubobjtype as
+ * PUBLICATIONOBJ_CURRSCHEMA to indicate the schema name should be
+ * set with the first schema in search_path.
+ */
+ if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = (pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA) ?
+ PUBLICATIONOBJ_REL_IN_SCHEMA : pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..5911824d09 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -114,10 +109,18 @@ extern List *GetAllTablesPublicationRelations(bool pubviaroot);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..8220c72469 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -487,7 +488,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..35a6b8ddde 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CONTINUATION, /* Continuation of previous type */
+ PUBLICATIONOBJ_CURRSCHEMA /* Get the first element from search_path */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v35-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v35-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From adf68c5623321ef9e97ccd040032a97e49565379 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v35 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 200 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 33 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 354 insertions(+), 52 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..93ed3344d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4126,6 +4134,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4213,6 +4309,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10619,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18854,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0ef..f1ccc2a9a1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5053,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6348,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6413,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6449,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6459,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6485,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..7a55ad3fb6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,27 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v35-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v35-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From cb0212a9b48dd96c4ab17892fa0e7a52ac96fe54 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v35 3/5] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 533 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 250 +++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
8 files changed, 1011 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..b4dd0f5444 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..2427d01bdf 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,77 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +165,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +341,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +387,466 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+ testpub5_forschema
+ testpub6_forschema
+(3 rows)
+
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "CURRENT_SCHEMA"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'CURRENT_SCHEMA' ORDER BY 1;
+ pubname
+--------------------
+ testpub4_forschema
+ testpub5_forschema
+ testpub6_forschema
+(3 rows)
+
+\dRp+ testpub5_forschema
+ Publication testpub5_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "CURRENT_SCHEMA"
+ "public"
+
+\dRp+ testpub6_forschema
+ Publication testpub6_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200adcb..433388ee64 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..9a64588211 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +187,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +199,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +225,222 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+\dRp+ testpub4_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'CURRENT_SCHEMA' ORDER BY 1;
+
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v35-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v35-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 70d1105216b22b4d8445c6bd7300fdb0e4286b2b Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v35 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 88 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 ++++++++++++++++++--
3 files changed, 207 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 384e6eaa3b..80df6dd98f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..981e38189f 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +117,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +170,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..938237ae05 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v35-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v35-0005-Implemented-pg_publication_objects-view.patchDownload
From 9d9b6d07bc3c83594e71bc33188da71a363206f8 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v35 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 80df6dd98f..a8ba883dd1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Wed, Sep 29, 2021 at 9:07 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Tues, Sep 28, 2021 10:46 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v34 patch has the changes for the same.
Thanks for updating the patch.
Here are a few comments.1)
+ * ALL TABLES IN SCHEMA schema [[, ...][[ -> [
Modified
2)
+ /* ALTER PUBLICATION ... ADD/DROP TABLE/ALL TABLES IN SCHEMA parameters */The two '/' seems a bit unclear and it doesn't mention the SET case.
Maybe we can write like:/* parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication objects */
Modified
3) + /* + * Check if setting the relation to a different schema will result in the + * publication having schema and same schema's table in the publication. + */ + if (stmt->objectType == OBJECT_TABLE) + { + ListCell *lc; + List *schemaPubids = GetSchemaPublications(nspOid); + foreach(lc, schemaPubids) + { + Oid pubid = lfirst_oid(lc); + if (list_member_oid(GetPublicationRelations(pubid, PUBLICATION_PART_ALL), + relid)) + ereport(ERROR,How about we check this case like the following ?
List *schemaPubids = GetSchemaPublications(nspOid);
List *relPubids = GetRelationPublications(RelationGetRelid(rel));
if (list_intersection(schemaPubids, relPubids))
ereport(ERROR, ...
Modified it with slight changes without using list_intersection.
These comments are handled in the v35 version patch attached at [1]/messages/by-id/CALDaNm2yJOEPCqR=gTMEwveJujH9c9_z4LhKmk2T3vZH7T1DLQ@mail.gmail.com
[1]: /messages/by-id/CALDaNm2yJOEPCqR=gTMEwveJujH9c9_z4LhKmk2T3vZH7T1DLQ@mail.gmail.com
Regards,
VIgnesh
On Wed, Sep 29, 2021 at 10:46 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Sep 28, 2021 at 8:15 PM vignesh C <vignesh21@gmail.com> wrote:
On Mon, Sep 27, 2021 at 12:15 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:Attached v34 patch has the changes for the same.
Few comments on v34-0001-Added-schema-level-support-for-publication
==========================================================
1.
+ * This rule parses publication object with and without keyword prefix.I think we should write it as: "This rule parses publication objects
with and without keyword prefixes."
Modified
2. + * For the object without keyword prefix, we cannot just use relation_expr here, + * because some extended expression in relation_expr cannot be used as a/expression/expressions
Modified
3. +/* + * Process pubobjspec_list to check for errors in any of the objects and + * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType + * type.
Modified to remove type as discussed offline.
4. + /* + * Check if setting the relation to a different schema will result in the + * publication having schema and same schema's table in the publication. + */ + if (stmt->objectType == OBJECT_TABLE) + { + ListCell *lc; + List *schemaPubids = GetSchemaPublications(nspOid); + foreach(lc, schemaPubids) + { + Oid pubid = lfirst_oid(lc); + if (list_member_oid(GetPublicationRelations(pubid, PUBLICATION_PART_ALL), + relid)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot move table \"%s\" to schema \"%s\"", + RelationGetRelationName(rel), stmt->newschema), + errdetail("Altering table will result in having schema \"%s\" and same schema's table \"%s\" in the publication \"%s\" which is not supported.", + stmt->newschema, + RelationGetRelationName(rel), + get_publication_name(pubid, false))); + } + }Let's slightly change the comment as: "Check that setting the relation
to a different schema won't result in the publication having schema
and the same schema's table." and errdetail as: "The schema \"%s\" and
same schema's table \"%s\" cannot be part of the same publication
\"%s\"."Maybe it is better to specify that this will disallow the partition table case.
Modified, I did not add the above as we are allowing it.
5. ObjectsInPublicationToOids() { .. + pubobj = (PublicationObjSpec *) lfirst(cell); + if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)It is better to keep an empty line between above two lines.
Modified
6.
List *schemaPubids = GetSchemaPublications(nspOid);
foreach(lc, schemaPubids)
..
Oid pubid = lfirst_oid(lc);
if (list_member_oid(GetPublicationRelations(pubid, PUBLICATION_PART_ALL),Add an empty line between each of the above two lines.
Modified
7. + /* + * Schema lock is held until the publication is altered to prevent + * concurrent schema deletion. No need to unlock the schemas, the locks + * will be released automatically at the end of alter publication command. + */ + LockSchemaList(schemaidlist);I think it is better to add a similar comment at other places where
this function is called. And we can shorten the comment atop
LockSchemaList to something like: "The schemas specified in the schema
list are locked in AccessShareLock mode in order to prevent concurrent
schema deletion."
Modified
8. In CreatePublication(), the check if (stmt->for_all_tables) can be
the first check and then in else if we can process tables and schemas.
Modified
9. AlterPublication() { .. + /* Lock the publication so nobody else can do anything with it. */ + LockDatabaseObject(PublicationRelationId, pubform->oid, 0, + AccessExclusiveLock);I think it is better to say why we need this lock. So, can we change
the comment to something like: "Lock the publication so nobody else
can do anything with it. This prevents concurrent alter to add
table(s) that were already going to become part of the publication by
adding corresponding schema(s) via this command and similarly it will
prevent the concurrent addition of schema(s) for which there is any
corresponding table being added by this command."
Modified
These comments are handled in the v35 version patch attached at [1]/messages/by-id/CALDaNm2yJOEPCqR=gTMEwveJujH9c9_z4LhKmk2T3vZH7T1DLQ@mail.gmail.com
[1]: /messages/by-id/CALDaNm2yJOEPCqR=gTMEwveJujH9c9_z4LhKmk2T3vZH7T1DLQ@mail.gmail.com
Regards,
Vignesh
On Wed, Sep 29, 2021 at 11:49 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Wed, Sep 29, 2021 at 3:16 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
4. + /* + * Check if setting the relation to a different schema will result in the + * publication having schema and same schema's table in the publication. + */ + if (stmt->objectType == OBJECT_TABLE) + { + ListCell *lc; + List *schemaPubids = GetSchemaPublications(nspOid); + foreach(lc, schemaPubids) + { + Oid pubid = lfirst_oid(lc); + if (list_member_oid(GetPublicationRelations(pubid, PUBLICATION_PART_ALL), + relid)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot move table \"%s\" to schema \"%s\"", + RelationGetRelationName(rel), stmt->newschema), + errdetail("Altering table will result in having schema \"%s\" and same schema's table \"%s\" in the publication \"%s\" which is not supported.", + stmt->newschema, + RelationGetRelationName(rel), + get_publication_name(pubid, false))); + } + }Let's slightly change the comment as: "Check that setting the relation
to a different schema won't result in the publication having schema
and the same schema's table." and errdetail as: "The schema \"%s\" and
same schema's table \"%s\" cannot be part of the same publication
\"%s\"."Since this code is in AlterTableNamespace() and the relation being
checked may or may not be part of a publication, I'd use "a
publication" instead of "the publication" in the comment.
Also, I'd say that we're doing the check because the mentioned
combination is not supported.i.e. "Check that setting the relation to a different schema won't
result in a publication having both a schema and the same schema's
table, as this is not supported."
Thanks for the comment, I have handled it in the v35 version patch
attached at [1]/messages/by-id/CALDaNm2yJOEPCqR=gTMEwveJujH9c9_z4LhKmk2T3vZH7T1DLQ@mail.gmail.com
[1]: /messages/by-id/CALDaNm2yJOEPCqR=gTMEwveJujH9c9_z4LhKmk2T3vZH7T1DLQ@mail.gmail.com
Regards,
Vignesh
On Thu, Sep 30, 2021 at 3:39 PM vignesh C <vignesh21@gmail.com> wrote:
The suggested change works, I have modified it in the attached patch.
I have reviewed the latest version and made a number of changes to the
0001 patch. The changes are in v1-0001-Changes-by-Amit. It includes
(a) Changed preprocess_pubobj_list() to make the code easy to
understand, (b) the handling of few variables was missing in equal
function, (c) the ordering of functions, and a few parameters were not
matching .c and .h files, (d) added/edited multiple comments and other
cosmetic changes.
Apart from that, I have few other comments:
1. It seems you have started using unique list variants in
GetPubPartitionOptionRelations because one of its caller
GetSchemaPublicationRelations need it. I think the unique variants are
costlier, so isn't it better to use it where it is required? I think
it would be good to use in GetPubPartitionOptionRelations, if most of
its caller requires the same.
2. In GetSchemaPublicationRelations(), I think we need to perform a
second scan using RELKIND_PARTITIONED_TABLE only if we
publish_via_root (aka pub_partopt is PUBLICATION_PART_ROOT). This is
what we are doing in GetAllTablesPublicationRelations. Is there a
reason to be different here?
3.
@@ -538,7 +788,7 @@ RemovePublicationById(Oid pubid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication %u", pubid);
- pubform = (Form_pg_publication)GETSTRUCT(tup);
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
We don't need the above change for this patch. I think this may be due
pgindent but we can do this separately rather than as part of this
patch.
--
With Regards,
Amit Kapila.
Attachments:
v35-0001-Added-schema-level-support-for-publication.patchapplication/octet-stream; name=v35-0001-Added-schema-level-support-for-publication.patchDownload
From 4d0fb9c9f87436cd8bfb5f326a99773208ce14f2 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 23 Sep 2021 13:40:27 +0800
Subject: [PATCH v35 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 ++++++
src/backend/catalog/pg_publication.c | 297 +++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 506 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 31 +-
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 16 +-
src/backend/parser/gram.y | 297 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 17 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 15 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1316 insertions(+), 184 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..4e8ccdabc6 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -152,7 +177,7 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
NULL);
if (pub_partopt == PUBLICATION_PART_ALL)
- result = list_concat(result, all_parts);
+ result = list_concat_unique_oid(result, all_parts);
else if (pub_partopt == PUBLICATION_PART_LEAF)
{
ListCell *lc;
@@ -162,14 +187,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
Oid partOid = lfirst_oid(lc);
if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
- result = lappend_oid(result, partOid);
+ result = list_append_unique_oid(result, partOid);
}
}
else
Assert(false);
}
else
- result = lappend_oid(result, relid);
+ result = list_append_unique_oid(result, relid);
return result;
}
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,84 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ check_publication_add_schema(schemaid);
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -328,6 +431,73 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -366,7 +536,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets list of all relation published by FOR ALL TABLES publication(s).
+ * Gets the list of relations published.
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -428,6 +598,101 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm) &&
+ !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ relForm->oid);
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -557,10 +822,22 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..2f4d0b1544 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,95 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ Assert(0);
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +249,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +320,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * The schemas specified in the schema list are locked in
+ * AccessShareLock mode in order to prevent concurrent schema
+ * deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +440,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +489,32 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +523,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +535,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +546,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +568,110 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add/Remove/Set all tables from schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * The schemas specified in the schema list are locked in AccessShareLock
+ * mode in order to prevent concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * The schemas specified in the schema list are locked in
+ * AccessShareLock mode in order to prevent concurrent schema
+ * deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in given publication and throws
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +701,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -538,7 +787,7 @@ RemovePublicationById(Oid pubid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication %u", pubid);
- pubform = (Form_pg_publication)GETSTRUCT(tup);
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
/* Invalidate relcache so that publication info is rebuilt. */
if (pubform->puballtables)
@@ -552,9 +801,84 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /* Invalidate relcache so that publication info is rebuilt. */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to prevent concurrent schema deletion. No need to unlock the
+ * schemas, the locks will be released at the end of the command.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +892,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +916,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +949,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +970,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +989,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +997,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1009,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1049,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1071,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ff97b618e6..445cae945b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
@@ -15960,6 +15962,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..553cd834e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4817,7 +4817,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4830,9 +4830,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4958,12 +4958,15 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..054b2d94e5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2302,7 +2302,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2314,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3134,9 +3134,11 @@ _equalBitString(const BitString *a, const BitString *b)
}
static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
{
- COMPARE_NODE_FIELD(relation);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
return true;
}
@@ -3894,8 +3896,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..4f11ef5cbc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -554,6 +560,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17045,6 +17100,41 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r = makeRangeVar(NULL, NULL, location);
+
+ check_qualified_name(namelist, yyscanner);
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17195,6 +17285,69 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name was specified as CURRENT_SCHEMA */
+ if (!pubobj->name && !pubobj->rangevar)
+ pubobj->rangevar = makeRangeVar(NULL, "CURRENT_SCHEMA",
+ pubobj->location);
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ {
+ /*
+ * Schema name was specified as CURRENT_SCHEMA, set pubobjtype as
+ * PUBLICATIONOBJ_CURRSCHEMA to indicate the schema name should be
+ * set with the first schema in search_path.
+ */
+ if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = (pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA) ?
+ PUBLICATIONOBJ_REL_IN_SCHEMA : pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..e902ed73da 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..d6c656edc8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..5911824d09 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -114,10 +109,18 @@ extern List *GetAllTablesPublicationRelations(bool pubviaroot);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..8220c72469 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,7 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
+ T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -487,7 +488,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..35a6b8ddde 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CONTINUATION, /* Continuation of previous type */
+ PUBLICATIONOBJ_CURRSCHEMA /* Get the first element from search_path */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v1-0001-Changes-by-Amit.patchapplication/octet-stream; name=v1-0001-Changes-by-Amit.patchDownload
From d5524bb4287dd6c9d1e69404561dffc2b8c7c3e7 Mon Sep 17 00:00:00 2001
From: Amit Kapila <akapila@postgresql.org>
Date: Fri, 1 Oct 2021 17:20:29 +0530
Subject: [PATCH v1] Changes by Amit.
---
src/backend/catalog/pg_publication.c | 146 ++++++++++----------
src/backend/commands/publicationcmds.c | 34 +++--
src/backend/nodes/copyfuncs.c | 26 ++--
src/backend/nodes/equalfuncs.c | 22 +--
src/backend/parser/gram.y | 29 ++--
src/backend/replication/pgoutput/pgoutput.c | 2 +-
src/backend/utils/cache/syscache.c | 2 +-
src/include/catalog/pg_publication.h | 10 +-
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 4 +-
10 files changed, 147 insertions(+), 130 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 4e8ccdabc6..644eb17680 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -303,8 +303,6 @@ publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
ObjectAddress myself,
referenced;
- check_publication_add_schema(schemaid);
-
rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
/*
@@ -327,6 +325,8 @@ publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
get_namespace_name(schemaid), pub->name)));
}
+ check_publication_add_schema(schemaid);
+
/* Form a tuple */
memset(values, 0, sizeof(values));
memset(nulls, false, sizeof(nulls));
@@ -358,7 +358,10 @@ publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
/* Close the table */
table_close(rel, RowExclusiveLock);
- /* Invalidate relcache so that publication info is rebuilt. */
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the partitions.
+ */
schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
InvalidatePublicationRels(schemaRels);
@@ -431,73 +434,6 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
return result;
}
-/*
- * Gets the list of schema oids for a publication.
- *
- * This should only be used FOR ALL TABLES IN SCHEMA publications.
- */
-List *
-GetPublicationSchemas(Oid pubid)
-{
- List *result = NIL;
- Relation pubschsrel;
- ScanKeyData scankey;
- SysScanDesc scan;
- HeapTuple tup;
-
- /* Find all publications associated with the schema */
- pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
-
- ScanKeyInit(&scankey,
- Anum_pg_publication_namespace_pnpubid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(pubid));
-
- scan = systable_beginscan(pubschsrel,
- PublicationNamespacePnnspidPnpubidIndexId,
- true, NULL, 1, &scankey);
- while (HeapTupleIsValid(tup = systable_getnext(scan)))
- {
- Form_pg_publication_namespace pubsch;
-
- pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
-
- result = lappend_oid(result, pubsch->pnnspid);
- }
-
- systable_endscan(scan);
- table_close(pubschsrel, AccessShareLock);
-
- return result;
-}
-
-
-/*
- * Gets the list of publication oids associated with a specified schema.
- */
-List *
-GetSchemaPublications(Oid schemaid)
-{
- List *result = NIL;
- CatCList *pubschlist;
- int i;
-
- /* Find all publications associated with the schema */
- pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
- ObjectIdGetDatum(schemaid));
- for (i = 0; i < pubschlist->n_members; i++)
- {
- HeapTuple tup = &pubschlist->members[i]->tuple;
- Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
-
- result = lappend_oid(result, pubid);
- }
-
- ReleaseSysCacheList(pubschlist);
-
- return result;
-}
-
/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
@@ -536,7 +472,7 @@ GetAllTablesPublications(void)
}
/*
- * Gets the list of relations published.
+ * Gets the list of relation published by FOR ALL TABLES publication(s).
*
* If the publication publishes partition changes via their respective root
* partitioned tables, we must exclude partitions in favor of including the
@@ -598,6 +534,72 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List*
+GetPublicationSchemas(Oid pubid)
+{
+ List* result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List*
+GetSchemaPublications(Oid schemaid)
+{
+ List* result = NIL;
+ CatCList* pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
/*
* Get the list of publishable relation oids for a specified schema.
*/
@@ -820,7 +822,9 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
{
List *relids,
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 2f4d0b1544..73d46612c4 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -176,7 +176,6 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
*schemas = list_append_unique_oid(*schemas, schemaid);
break;
case PUBLICATIONOBJ_CURRSCHEMA:
-
search_path = fetch_search_path(false);
if (search_path == NIL) /* nothing valid in search_path? */
ereport(ERROR,
@@ -190,7 +189,9 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
*schemas = list_append_unique_oid(*schemas, schemaid);
break;
default:
- Assert(0);
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
}
}
}
@@ -320,6 +321,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
+ /* Associate objects with the publication. */
if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
@@ -350,9 +352,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
/*
- * The schemas specified in the schema list are locked in
- * AccessShareLock mode in order to prevent concurrent schema
- * deletion.
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
*/
LockSchemaList(schemaidlist);
PublicationAddSchemas(puboid, schemaidlist, true, NULL);
@@ -496,6 +497,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
if (!tables && stmt->action != DEFELEM_SET)
return;
@@ -571,7 +576,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Alter the publication schemas.
*
- * Add/Remove/Set all tables from schemas to/from publication.
+ * Add or remove schemas to/from publication.
*/
static void
AlterPublicationSchemas(AlterPublicationStmt *stmt,
@@ -583,8 +588,8 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
return;
/*
- * The schemas specified in the schema list are locked in AccessShareLock
- * mode in order to prevent concurrent schema deletion.
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
*/
LockSchemaList(schemaidlist);
if (stmt->action == DEFELEM_ADD)
@@ -612,9 +617,8 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
delschemas = list_difference_oid(oldschemaids, schemaidlist);
/*
- * The schemas specified in the schema list are locked in
- * AccessShareLock mode in order to prevent concurrent schema
- * deletion.
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
*/
LockSchemaList(delschemas);
@@ -820,7 +824,10 @@ RemovePublicationSchemaById(Oid psoid)
pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
- /* Invalidate relcache so that publication info is rebuilt. */
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the partitions.
+ */
schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
PUBLICATION_PART_ALL);
InvalidatePublicationRels(schemaRels);
@@ -834,8 +841,7 @@ RemovePublicationSchemaById(Oid psoid)
/*
* The schemas specified in the schema list are locked in AccessShareLock mode
- * in order to prevent concurrent schema deletion. No need to unlock the
- * schemas, the locks will be released at the end of the command.
+ * in order to prevent concurrent schema deletion.
*/
static void
LockSchemaList(List *schemalist)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 553cd834e6..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,6 +4810,19 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
+{
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
+
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static CreatePublicationStmt *
_copyCreatePublicationStmt(const CreatePublicationStmt *from)
{
@@ -4958,19 +4971,6 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationObjSpec *
-_copyPublicationObject(const PublicationObjSpec *from)
-{
- PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
-
- COPY_SCALAR_FIELD(pubobjtype);
- COPY_STRING_FIELD(name);
- COPY_NODE_FIELD(rangevar);
- COPY_LOCATION_FIELD(location);
-
- return newnode;
-}
-
/*
* copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 054b2d94e5..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -3038,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3133,16 +3145,6 @@ _equalBitString(const BitString *a, const BitString *b)
return true;
}
-static bool
-_equalPublicationObject(const PublicationObjSpec *a,
- const PublicationObjSpec *b)
-{
- COMPARE_STRING_FIELD(name);
- COMPARE_NODE_FIELD(rangevar);
-
- return true;
-}
-
/*
* equal
* returns whether two nodes are equal
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4f11ef5cbc..e7b33de27f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -559,8 +559,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
-
%type <publicationobjectspec> PublicationObjSpec
+
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
@@ -17108,9 +17108,11 @@ static RangeVar *
makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
core_yyscan_t yyscanner)
{
- RangeVar *r = makeRangeVar(NULL, NULL, location);
+ RangeVar *r;
check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
switch (list_length(namelist))
{
case 1:
@@ -17315,10 +17317,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name was specified as CURRENT_SCHEMA */
+ /* relation name or rangevar must be set for this type of object */
if (!pubobj->name && !pubobj->rangevar)
- pubobj->rangevar = makeRangeVar(NULL, "CURRENT_SCHEMA",
- pubobj->location);
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
else if (pubobj->name)
{
/* convert it to rangevar */
@@ -17327,14 +17331,16 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->name = NULL;
}
}
- else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
{
/*
- * Schema name was specified as CURRENT_SCHEMA, set pubobjtype as
- * PUBLICATIONOBJ_CURRSCHEMA to indicate the schema name should be
- * set with the first schema in search_path.
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
*/
- if (!pubobj->name && !pubobj->rangevar)
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
else if (!pubobj->name)
ereport(ERROR,
@@ -17343,8 +17349,7 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
parser_errposition(pubobj->location));
}
- prevobjtype = (pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA) ?
- PUBLICATIONOBJ_REL_IN_SCHEMA : pubobj->pubobjtype;
+ prevobjtype = pubobj->pubobjtype;
}
}
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index e902ed73da..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1358,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6c656edc8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -618,7 +618,7 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
- {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPCE */
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
PublicationNamespaceObjectIndexId,
1,
{
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 5911824d09..a4c894ec9d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -106,17 +106,17 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-extern List *GetPubPartitionOptionRelations(List *result,
- PublicationPartOpt pub_partopt,
- Oid relid);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
-extern List *GetAllSchemaPublicationRelations(Oid puboid,
- PublicationPartOpt pub_partopt);
extern List *GetSchemaPublicationRelations(Oid schemaid,
PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
+extern List *GetPubPartitionOptionRelations(List* result,
+ PublicationPartOpt pub_partopt,
+ Oid relid);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 8220c72469..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,7 +479,6 @@ typedef enum NodeTag
T_CTESearchClause,
T_CTECycleClause,
T_CommonTableExpr,
- T_PublicationObjSpec,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
@@ -488,6 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 35a6b8ddde..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -360,8 +360,8 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* Table type */
PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
- PUBLICATIONOBJ_CONTINUATION, /* Continuation of previous type */
- PUBLICATIONOBJ_CURRSCHEMA /* Get the first element from search_path */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
} PublicationObjSpecType;
typedef struct PublicationObjSpec
--
2.28.0.windows.1
On Sat, Oct 2, 2021 at 1:13 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Sep 30, 2021 at 3:39 PM vignesh C <vignesh21@gmail.com> wrote:
The suggested change works, I have modified it in the attached patch.
I have reviewed the latest version and made a number of changes to the
0001 patch. The changes are in v1-0001-Changes-by-Amit. It includes
(a) Changed preprocess_pubobj_list() to make the code easy to
understand, (b) the handling of few variables was missing in equal
function, (c) the ordering of functions, and a few parameters were not
matching .c and .h files, (d) added/edited multiple comments and other
cosmetic changes.
I have merged these changes into the main patch.
Apart from that, I have few other comments:
1. It seems you have started using unique list variants in
GetPubPartitionOptionRelations because one of its caller
GetSchemaPublicationRelations need it. I think the unique variants are
costlier, so isn't it better to use it where it is required? I think
it would be good to use in GetPubPartitionOptionRelations, if most of
its caller requires the same.
I have removed unique list changes from GetPubPartitionOptionRelations
and handled it in GetSchemaPublicationRelations.
2. In GetSchemaPublicationRelations(), I think we need to perform a
second scan using RELKIND_PARTITIONED_TABLE only if we
publish_via_root (aka pub_partopt is PUBLICATION_PART_ROOT). This is
what we are doing in GetAllTablesPublicationRelations. Is there a
reason to be different here?
In the first table scan we are getting all the ordinary tables present
in the schema. In the second table scan we will get all the
partitioned table present in the schema and the relations will be
added based on pub_partopt. I felt if we have the check we will not
get the relations in the following case:
create schema sch1;
create schema sch2;
create table sch1.p (a int) partition by list (a);
create table sch2.c1 partition of sch1.p for values in (1);
3.
@@ -538,7 +788,7 @@ RemovePublicationById(Oid pubid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication %u", pubid);- pubform = (Form_pg_publication)GETSTRUCT(tup); + pubform = (Form_pg_publication) GETSTRUCT(tup);We don't need the above change for this patch. I think this may be due
pgindent but we can do this separately rather than as part of this
patch.
Removed this change.
Attached v36 patch has the changes for the same.
Regards,
Vignesh
Attachments:
v36-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v36-0001-Added-schema-level-support-for-publication.patchDownload
From dafe31065921fb62133fa9f6f8acf67e03a1baf8 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 23 Sep 2021 13:40:27 +0800
Subject: [PATCH v36 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 300 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 510 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 31 +-
src/backend/nodes/copyfuncs.c | 33 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 ++++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 21 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1351 insertions(+), 195 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..2712abc0c9 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,88 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +535,173 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm) &&
+ !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ {
+ List *partitionrels = NIL;
+
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +829,26 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..668a303044 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,96 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +321,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
-
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +573,109 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in given publication and throws
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +705,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +805,86 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * The schemas specified in the schema list are locked in AccessShareLock mode
+ * in order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +898,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +922,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +955,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +976,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +995,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1003,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1015,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1055,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1077,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ff97b618e6..445cae945b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15832,7 +15833,8 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
- list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
+ (list_length(GetRelationPublications(RelationGetRelid(rel))) > 0 ||
+ list_length(GetSchemaPublications(rel->rd_rel->relnamespace)) > 0))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
@@ -15960,6 +15962,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,6 +4810,19 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
+{
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
+
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static CreatePublicationStmt *
_copyCreatePublicationStmt(const CreatePublicationStmt *from)
{
@@ -4817,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4830,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4958,16 +4971,6 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
-{
- PublicationTable *newnode = makeNode(PublicationTable);
-
- COPY_NODE_FIELD(relation);
-
- return newnode;
-}
-
/*
* copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
*
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2302,7 +2302,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2314,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3038,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3133,14 +3145,6 @@ _equalBitString(const BitString *a, const BitString *b)
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
/*
* equal
* returns whether two nodes are equal
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..e7b33de27f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17045,6 +17100,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17195,6 +17287,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..a4c894ec9d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-extern List *GetPubPartitionOptionRelations(List *result,
- PublicationPartOpt pub_partopt,
- Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern List *GetPubPartitionOptionRelations(List* result,
+ PublicationPartOpt pub_partopt,
+ Oid relid);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v36-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v36-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 4d7355a2528494b990efb72f1dffd2eb2293e85d Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v36 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 15 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 200 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 33 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 354 insertions(+), 52 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..baf44424c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..93ed3344d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
/*
* In any case, a namespace can be excluded by an exclusion switch
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
@@ -4126,6 +4134,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * Ignore publication membership of schema whose definitions are not
+ * to be dumped.
+ */
+ if (!(nspinfo->dobj.dump & DUMP_COMPONENT_PUBSCHEMA))
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationTable(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4213,6 +4309,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationSchema
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationSchema(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10619,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationSchema(fout, (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18854,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..375917a532 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -94,6 +95,7 @@ typedef uint32 DumpComponents; /* a bitmask of dump object components */
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_PUBSCHEMA (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -631,6 +633,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +750,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0ef..f1ccc2a9a1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5053,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6348,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6413,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6449,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6459,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6485,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..7a55ad3fb6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,27 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (HeadMatches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v36-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v36-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 8c2a9ea5ff0e48d9b08c47ac029a512bb9f5b77e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v36 3/5] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 533 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 250 +++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
8 files changed, 1011 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..b4dd0f5444 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..2427d01bdf 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,77 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +165,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +341,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +387,466 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+ testpub5_forschema
+ testpub6_forschema
+(3 rows)
+
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "CURRENT_SCHEMA"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'CURRENT_SCHEMA' ORDER BY 1;
+ pubname
+--------------------
+ testpub4_forschema
+ testpub5_forschema
+ testpub6_forschema
+(3 rows)
+
+\dRp+ testpub5_forschema
+ Publication testpub5_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "CURRENT_SCHEMA"
+ "public"
+
+\dRp+ testpub6_forschema
+ Publication testpub6_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+ERROR: cannot update table "child" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to table pub_testpart1.parent
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200adcb..433388ee64 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..9a64588211 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +187,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +199,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +225,222 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+\dRp+ testpub4_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'CURRENT_SCHEMA' ORDER BY 1;
+
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child partition of pub_testpart1.parent for values in (1);
+INSERT INTO pub_testpart2.child values(1);
+UPDATE pub_testpart2.child set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v36-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v36-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 6ed4fc57cfc0eefe975b166ba4f6df8db86b9f6d Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v36 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 88 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 ++++++++++++++++++--
3 files changed, 207 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 384e6eaa3b..80df6dd98f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6236,6 +6241,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11276,9 +11342,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..981e38189f 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +117,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +170,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..938237ae05 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v36-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v36-0005-Implemented-pg_publication_objects-view.patchDownload
From 531025e3fbb378ea9bf9bdb30cc77b23eb43fe51 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v36 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 80df6dd98f..a8ba883dd1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9501,6 +9501,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11330,6 +11335,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Sun, Oct 3, 2021 at 11:25 PM vignesh C <vignesh21@gmail.com> wrote:
On Sat, Oct 2, 2021 at 1:13 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
2. In GetSchemaPublicationRelations(), I think we need to perform a
second scan using RELKIND_PARTITIONED_TABLE only if we
publish_via_root (aka pub_partopt is PUBLICATION_PART_ROOT). This is
what we are doing in GetAllTablesPublicationRelations. Is there a
reason to be different here?In the first table scan we are getting all the ordinary tables present
in the schema. In the second table scan we will get all the
partitioned table present in the schema and the relations will be
added based on pub_partopt. I felt if we have the check we will not
get the relations in the following case:
create schema sch1;
create schema sch2;
create table sch1.p (a int) partition by list (a);
create table sch2.c1 partition of sch1.p for values in (1);
But we don't need to get the partitioned tables for the invalidations,
see the corresponding case for tables. So, I am not sure why you have
used two scans to the system table for such scenarios?
Few additional comments on
v36-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN:
=========================================================================
1.
@@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete,
p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, "
+ "p.pubdelete, p.pubtruncate, p.pubviaroot "
"FROM pg_publication p",
username_subquery);
else if (fout->remoteVersion >= 110000)
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
else
appendPQExpBuffer(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"(%s p.pubowner) AS rolname, "
- "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 pubviaroot "
"FROM pg_publication p",
username_subquery);
Is there a reason to change this part of the code?
2.
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication tables in schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
I think for the above change, the first should be changed to "reading
publication membership of tables" and the second one should be changed
to "reading publication membership of schemas".
3. The function names getPublicationNamespaces and
dumpPublicationSchema are not in sync. Let's name the second one as
dumpPublicationNamespace.
4. It is not clear to me why the patch has introduced a new component
type object DUMP_COMPONENT_PUBSCHEMA. In particular, in the below code
if we are already setting DUMP_COMPONENT_ALL, how the additional
setting of DUMP_COMPONENT_PUBSCHEMA helps?
@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo,
Archive *fout)
if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER)
nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
}
else
+ {
nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL;
+ nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA;
+ }
--
With Regards,
Amit Kapila.
On Mon, Oct 4, 2021 at 4:55 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v36 patch has the changes for the same.
I have some comments on the v36-0001 patch:
src/backend/commands/publicationcmds.c
(1)
GetPublicationSchemas()
+ /* Find all publications associated with the schema */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
I think the comment is not correct. It should say:
+ /* Find all schemas associated with the publication */
(2)
AlterPublicationSchemas
I think that a comment should be added for the following lines,
something like the comment used in the similar check in
AlterPublicationTables():
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
(3)
CheckAlterPublication
Minor comment fix suggested:
BEFORE:
+ * Check if relations and schemas can be in given publication and throws
AFTER:
+ * Check if relations and schemas can be in a given publication and throw
(4)
LockSchemaList()
Suggest re-word of comment, to match imperative comment style used
elsewhere in this code.
BEFORE:
+ * The schemas specified in the schema list are locked in AccessShareLock mode
AFTER:
+ * Lock the schemas specified in the schema list in AccessShareLock mode
src/backend/commands/tablecmds.c
(5)
Code has been added to prevent a table being set (via ALTER TABLE) to
UNLOGGED if it is part of a publication, but I found that I could
still add all tables of a schema having a table that is UNLOGGED:
postgres=# create schema sch;
CREATE SCHEMA
postgres=# create unlogged table sch.test(i int);
CREATE TABLE
postgres=# create publication pub for table sch.test;
ERROR: cannot add relation "test" to publication
DETAIL: Temporary and unlogged relations cannot be replicated.
postgres=# create publication pub for all tables in schema sch;
CREATE PUBLICATION
Regards,
Greg Nancarrow
Fujitsu Australia
On Tue, Oct 5, 2021 at 6:57 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
(5)
Code has been added to prevent a table being set (via ALTER TABLE) to
UNLOGGED if it is part of a publication, but I found that I could
still add all tables of a schema having a table that is UNLOGGED:postgres=# create schema sch;
CREATE SCHEMA
postgres=# create unlogged table sch.test(i int);
CREATE TABLE
postgres=# create publication pub for table sch.test;
ERROR: cannot add relation "test" to publication
DETAIL: Temporary and unlogged relations cannot be replicated.
postgres=# create publication pub for all tables in schema sch;
CREATE PUBLICATION
What about when you use "create publication pub for all tables;"? I
think that also works, now on similar lines shouldn't the behavior of
"all tables in schema" publication be the same? I mean if we want we
can detect and give an error but is it advisable to give an error if
there are just one or few tables in schema that are unlogged?
--
With Regards,
Amit Kapila.
On Tue, Oct 5, 2021 at 3:11 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
Code has been added to prevent a table being set (via ALTER TABLE) to
UNLOGGED if it is part of a publication, but I found that I could
still add all tables of a schema having a table that is UNLOGGED:postgres=# create schema sch;
CREATE SCHEMA
postgres=# create unlogged table sch.test(i int);
CREATE TABLE
postgres=# create publication pub for table sch.test;
ERROR: cannot add relation "test" to publication
DETAIL: Temporary and unlogged relations cannot be replicated.
postgres=# create publication pub for all tables in schema sch;
CREATE PUBLICATIONWhat about when you use "create publication pub for all tables;"? I
think that also works, now on similar lines shouldn't the behavior of
"all tables in schema" publication be the same? I mean if we want we
can detect and give an error but is it advisable to give an error if
there are just one or few tables in schema that are unlogged?
OK, it seems that for the ALL TABLES case, there is no such error
check, and it just silently skips replication of any
temporary/unlogged tables. This is intentional, right?
I couldn't see any documentation specifically related to this, so I
think perhaps it should be updated to describe this behaviour. At the
moment, the existing documentation just states FOR TABLE that
"Temporary tables, unlogged tables, foreign tables, materialized
views, and regular views cannot be part of a publication".
Yes, I agree that ALL TABLES IN SCHEMA should behave the same as the
ALL TABLES case.
Problem is, shouldn't setting UNLOGGED on a table only then be
disallowed if that table was publicised using FOR TABLE?
With the patch applied:
postgres=# create publication pub3 for all tables in schema sch;
CREATE PUBLICATION
postgres=# create table sch.test3(i int);
CREATE TABLE
postgres=# alter table sch.test3 set unlogged;
ERROR: cannot change table "test3" to unlogged because it is part of
a publication
DETAIL: Unlogged relations cannot be replicated.
Regards,
Greg Nancarrow
Fujitsu Australia
On Tue, Oct 5, 2021 at 4:40 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
At the
moment, the existing documentation just states FOR TABLE that
"Temporary tables, unlogged tables, foreign tables, materialized
views, and regular views cannot be part of a publication".
Oh, I see that in the v36-0004 doc update patch, it has added the
following for CREATE PUBLICATION ... FOR ALL TABLES IN SCHEMA:
"Only persistent base tables and partitioned tables present in the
schema will be included as part of the publication. Temporary tables,
unlogged tables, foreign tables, materialized views, and regular views
from the schema will not be part of the publication."
Regards,
Greg Nancarrow
Fujitsu Australia
On Tue, Oct 5, 2021 at 11:10 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Oct 5, 2021 at 3:11 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
Code has been added to prevent a table being set (via ALTER TABLE) to
UNLOGGED if it is part of a publication, but I found that I could
still add all tables of a schema having a table that is UNLOGGED:postgres=# create schema sch;
CREATE SCHEMA
postgres=# create unlogged table sch.test(i int);
CREATE TABLE
postgres=# create publication pub for table sch.test;
ERROR: cannot add relation "test" to publication
DETAIL: Temporary and unlogged relations cannot be replicated.
postgres=# create publication pub for all tables in schema sch;
CREATE PUBLICATIONWhat about when you use "create publication pub for all tables;"? I
think that also works, now on similar lines shouldn't the behavior of
"all tables in schema" publication be the same? I mean if we want we
can detect and give an error but is it advisable to give an error if
there are just one or few tables in schema that are unlogged?
..
Yes, I agree that ALL TABLES IN SCHEMA should behave the same as the
ALL TABLES case.
Problem is, shouldn't setting UNLOGGED on a table only then be
disallowed if that table was publicised using FOR TABLE?
Right, I also think so. Let us see what Vignesh or others think on this matter.
--
With Regards,
Amit Kapila.
On Tue, Oct 5, 2021 at 4:41 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Oct 5, 2021 at 11:10 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Oct 5, 2021 at 3:11 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
Code has been added to prevent a table being set (via ALTER TABLE) to
UNLOGGED if it is part of a publication, but I found that I could
still add all tables of a schema having a table that is UNLOGGED:postgres=# create schema sch;
CREATE SCHEMA
postgres=# create unlogged table sch.test(i int);
CREATE TABLE
postgres=# create publication pub for table sch.test;
ERROR: cannot add relation "test" to publication
DETAIL: Temporary and unlogged relations cannot be replicated.
postgres=# create publication pub for all tables in schema sch;
CREATE PUBLICATIONWhat about when you use "create publication pub for all tables;"? I
think that also works, now on similar lines shouldn't the behavior of
"all tables in schema" publication be the same? I mean if we want we
can detect and give an error but is it advisable to give an error if
there are just one or few tables in schema that are unlogged?..
Yes, I agree that ALL TABLES IN SCHEMA should behave the same as the
ALL TABLES case.
Problem is, shouldn't setting UNLOGGED on a table only then be
disallowed if that table was publicised using FOR TABLE?Right, I also think so. Let us see what Vignesh or others think on this matter.
Even I felt ALL TABLES IN SCHEMA should behave the same way as the ALL
TABLES case. I will keep the create publication behavior as it is i.e.
to allow even if unlogged tables are present and change the below
alter table behavior which was throwing error to be successful to keep
it similar to ALL TABLES publication:
alter table sch.test3 set unlogged;
ERROR: cannot change table "test3" to unlogged because it is part of
a publication
DETAIL: Unlogged relations cannot be replicated.
Regards,
Vignesh
On Mon, Oct 4, 2021 at 5:30 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Sun, Oct 3, 2021 at 11:25 PM vignesh C <vignesh21@gmail.com> wrote:
On Sat, Oct 2, 2021 at 1:13 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
2. In GetSchemaPublicationRelations(), I think we need to perform a
second scan using RELKIND_PARTITIONED_TABLE only if we
publish_via_root (aka pub_partopt is PUBLICATION_PART_ROOT). This is
what we are doing in GetAllTablesPublicationRelations. Is there a
reason to be different here?In the first table scan we are getting all the ordinary tables present
in the schema. In the second table scan we will get all the
partitioned table present in the schema and the relations will be
added based on pub_partopt. I felt if we have the check we will not
get the relations in the following case:
create schema sch1;
create schema sch2;
create table sch1.p (a int) partition by list (a);
create table sch2.c1 partition of sch1.p for values in (1);But we don't need to get the partitioned tables for the invalidations,
see the corresponding case for tables. So, I am not sure why you have
used two scans to the system table for such scenarios?
The second loop is required to get the 'p' relkind relations like in
the below case:
create schema sch1;
create schema sch2;
create table sch1.p1 (a int) partition by list (a);
create table sch2.c1 partition of sch1.p1 for values in (1);
create table sch2.p2(a int) partition by list (a);
create table sch1.c2 partition of sch2.p2 for values in (1);
create publication pub1 for all tables in schema sch2;
The first loop will give us sch2.c1 relation and the second loop will
get sch2.p2, sch1.c2, this is required for schema publication as the
schema can have both r relkind and p relkind tables and all of them
need to be invalidated. This is not required in case of table
publication as we can get the required relations from that particular
table.
Few additional comments on v36-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN: ========================================================================= 1. @@ -3961,21 +3965,25 @@ getPublications(Archive *fout, int *numPublications) appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.pubname, " "(%s p.pubowner) AS rolname, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, " + "p.pubdelete, p.pubtruncate, p.pubviaroot " "FROM pg_publication p", username_subquery); else if (fout->remoteVersion >= 110000) appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.pubname, " "(%s p.pubowner) AS rolname, " - "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 pubviaroot " "FROM pg_publication p", username_subquery); else appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.pubname, " "(%s p.pubowner) AS rolname, " - "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 pubviaroot " "FROM pg_publication p", username_subquery);Is there a reason to change this part of the code?
It is not required, I have removed it.
2.
@@ -257,6 +257,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publication membership");
getPublicationTables(fout, tblinfo, numTables);+ pg_log_info("reading publication tables in schemas"); + getPublicationNamespaces(fout, nspinfo, numNamespaces);I think for the above change, the first should be changed to "reading
publication membership of tables" and the second one should be changed
to "reading publication membership of schemas".
Modified
3. The function names getPublicationNamespaces and
dumpPublicationSchema are not in sync. Let's name the second one as
dumpPublicationNamespace.
Modified
4. It is not clear to me why the patch has introduced a new component
type object DUMP_COMPONENT_PUBSCHEMA. In particular, in the below code
if we are already setting DUMP_COMPONENT_ALL, how the additional
setting of DUMP_COMPONENT_PUBSCHEMA helps?@@ -1631,9 +1631,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout) if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER) nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION; nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL; + nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA; } else + { nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL; + nsinfo->dobj.dump |= DUMP_COMPONENT_PUBSCHEMA; + }
I have removed DUMP_COMPONENT_PUBSCHEMA and used DUMP_COMPONENT_NONE
to identify the publications that should be dumped.
Attached v37 patch has the changes for the same.
Regards,
Vignesh
Attachments:
v37-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v37-0001-Added-schema-level-support-for-publication.patchDownload
From 04702a702dc1c7a31775c774f5131a29dc4e7aa5 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 23 Sep 2021 13:40:27 +0800
Subject: [PATCH v37 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 314 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 514 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 33 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 21 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1367 insertions(+), 194 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..70c10c7b39 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,88 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +535,187 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ *
+ * Schema will be having both ordinary('r') relkind tables and partitioned('p')
+ * relkind tables, so two rounds of scan are required.
+ *
+ * The first scan will get all the 'r' relkind tables for the specified schema,
+ * iterate the 'r' relkind tables and prepare a list of:
+ * 1) non partition table if pub_partopt is PUBLICATION_PART_ROOT
+ * 2) partition table and non partition table if pub_partopt is
+ * PUBLICATION_PART_LEAF.
+ *
+ * The second scan will get all the 'p'' relkind tables for the specified
+ * schema, iterate the 'p' relkind tables and prepare a list of:
+ * 1) partition table's child relations if pub_partopt is PUBLICATION_PART_LEAF
+ * 2) partition table if pub_partopt is PUBLICATION_PART_ROOT.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm) &&
+ !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ {
+ List *partitionrels = NIL;
+
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +843,26 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..55079a604d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,96 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +321,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +573,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +709,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +809,86 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +902,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +926,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +959,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +980,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +999,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1007,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1019,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1059,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1081,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ff97b618e6..792b1878e3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15960,6 +15961,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,6 +4810,19 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
+{
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
+
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static CreatePublicationStmt *
_copyCreatePublicationStmt(const CreatePublicationStmt *from)
{
@@ -4817,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4830,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -4958,16 +4971,6 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
-{
- PublicationTable *newnode = makeNode(PublicationTable);
-
- COPY_NODE_FIELD(relation);
-
- return newnode;
-}
-
/*
* copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
*
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 800f588b5c..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2302,7 +2302,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2314,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3038,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3133,14 +3145,6 @@ _equalBitString(const BitString *a, const BitString *b)
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
/*
* equal
* returns whether two nodes are equal
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..80e8bd0aba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17115,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17302,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..a4c894ec9d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-extern List *GetPubPartitionOptionRelations(List *result,
- PublicationPartOpt pub_partopt,
- Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern List *GetPubPartitionOptionRelations(List* result,
+ PublicationPartOpt pub_partopt,
+ Oid relid);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v37-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v37-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 7a341fbfa584f99afa82578f096ecaacb287bbc1 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v37 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 141 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 200 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 35 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 351 insertions(+), 55 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..fa7ec8ec3e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,14 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
+ * Publications have schemas, but those are ignored in decision making,
* because publications are only dumped when we are dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4126,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4292,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4301,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication tables in schema mapping
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10611,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18847,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..8e5db13774 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0ef..f1ccc2a9a1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,39 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5053,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname FROM pg_catalog.pg_publication p,\n"
+ "pg_catalog.pg_namespace n,\n"
+ "pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid AND\n"
+ "p.oid = pn.pnpubid AND n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6297,42 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ termPQExpBuffer(buf);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6348,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6413,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6449,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6459,20 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
- {
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
- PQclear(tabres);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6485,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..f8cd92ad9a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,29 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v37-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v37-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 8b1950436b14bf49672dd32e2fda414cc3c97e23 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v37 3/5] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 558 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 267 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
8 files changed, 1053 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..b4dd0f5444 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..cbd950c6d6 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,77 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +165,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +341,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +387,491 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+ testpub2_forschema
+(2 rows)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+ pubname
+--------------------
+ testpub2_forschema
+(1 row)
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+Tables from schemas:
+ "public"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+ pubname
+--------------------
+ testpub3_forschema
+ testpub5_forschema
+ testpub6_forschema
+(3 rows)
+
+\dRp+ testpub4_forschema
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "CURRENT_SCHEMA"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'CURRENT_SCHEMA' ORDER BY 1;
+ pubname
+--------------------
+ testpub4_forschema
+ testpub5_forschema
+ testpub6_forschema
+(3 rows)
+
+\dRp+ testpub5_forschema
+ Publication testpub5_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "CURRENT_SCHEMA"
+ "public"
+
+\dRp+ testpub6_forschema
+ Publication testpub6_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of schema having partition parent table and partition child table
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set all tables in schema on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200adcb..433388ee64 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..9035ec83f1 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,45 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from all tables publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to for table publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from for table publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to for table publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +187,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +199,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +225,239 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test2' ORDER BY 1;
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test3' ORDER BY 1;
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'public' ORDER BY 1;
+
+\dRp+ testpub4_forschema
+SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'CURRENT_SCHEMA' ORDER BY 1;
+
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having partition on different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of schema having partition parent table and partition child table
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set all tables in schema on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both for table and for all tables in schema.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of FOR ALL TABLES IN SCHEMA or FOR TABLE or FOR ALL TABLES
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..a3e17f20f1
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v37-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v37-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 4151e755342deca23a3b134bf4c2e5a9188d9c28 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v37 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 88 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 ++++++++++++++++++--
3 files changed, 207 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..92de24f6de 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6238,6 +6243,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..981e38189f 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +117,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +170,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..938237ae05 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v37-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v37-0005-Implemented-pg_publication_objects-view.patchDownload
From 6f7d0ace0db342f1127d2ebaf835bec90263e147 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v37 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 92de24f6de..a078302ac8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Tue, Oct 5, 2021 at 6:57 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Mon, Oct 4, 2021 at 4:55 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v36 patch has the changes for the same.
I have some comments on the v36-0001 patch:
src/backend/commands/publicationcmds.c
(1)
GetPublicationSchemas()+ /* Find all publications associated with the schema */ + pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);I think the comment is not correct. It should say:
+ /* Find all schemas associated with the publication */
Modified
(2)
AlterPublicationSchemasI think that a comment should be added for the following lines,
something like the comment used in the similar check in
AlterPublicationTables():+ if (!schemaidlist && stmt->action != DEFELEM_SET) + return;
Modified
(3)
CheckAlterPublicationMinor comment fix suggested:
BEFORE: + * Check if relations and schemas can be in given publication and throws AFTER: + * Check if relations and schemas can be in a given publication and throw
Modified
(4)
LockSchemaList()Suggest re-word of comment, to match imperative comment style used
elsewhere in this code.BEFORE: + * The schemas specified in the schema list are locked in AccessShareLock mode AFTER: + * Lock the schemas specified in the schema list in AccessShareLock mode
Modified
src/backend/commands/tablecmds.c
(5)
Code has been added to prevent a table being set (via ALTER TABLE) to
UNLOGGED if it is part of a publication, but I found that I could
still add all tables of a schema having a table that is UNLOGGED:postgres=# create schema sch;
CREATE SCHEMA
postgres=# create unlogged table sch.test(i int);
CREATE TABLE
postgres=# create publication pub for table sch.test;
ERROR: cannot add relation "test" to publication
DETAIL: Temporary and unlogged relations cannot be replicated.
postgres=# create publication pub for all tables in schema sch;
CREATE PUBLICATION
I have changed the alter table behavior to allow setting it to an
unlogged table to keep the behavior similar to "ALL TABLES"
publication. I have kept the create publication behavior as it is, it
will be similar to "ALL TABLES" publication i.e to allow create
publication even if there are unlogged tables present.
These comments are handled in the v37 patch attached at [1]/messages/by-id/CALDaNm0ON=012jGC3oquSVVWTWXhHG0q8yOyRROVGFR9PjWa-g@mail.gmail.com.
[1]: /messages/by-id/CALDaNm0ON=012jGC3oquSVVWTWXhHG0q8yOyRROVGFR9PjWa-g@mail.gmail.com
Regards,
Vignesh
On Wed, Oct 6, 2021 at 4:42 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v37 patch has the changes for the same.
A small issue I noticed is that using "\dS" in PSQL shows UNLOGGED
tables as belonging to a publication, if the table belongs to a schema
that was added to the publication using ALL TABLES IN SCHEMA (yet
doesn't show as part of an ALL TABLES publication).
Since publication of UNLOGGED tables is silently skipped in the case
of ALL TABLES and ALL TABLES IN SCHEMA, it shouldn't show as belonging
to the publication, right?
test=# \dRp+ pub2
Publication pub2
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | t | t | t | t | t | f
(1 row)
test=# \dRp+ pub
Publication pub
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | t
Tables from schemas:
"sch1"
test=# \dS sch1.test2
Unlogged table "sch1.test2"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
i | integer | | |
Publications:
"pub"
Regards,
Greg Nancarrow
Fujitsu Australia
On Wed, Oct 6, 2021 at 11:12 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v37 patch has the changes for the same.
Few comments:
==============
v37-0001-Added-schema-level-support-for-publication
1.
+ *
+ * The first scan will get all the 'r' relkind tables for the specified schema,
+ * iterate the 'r' relkind tables and prepare a list of:
+ * 1) non partition table if pub_partopt is PUBLICATION_PART_ROOT
+ * 2) partition table and non partition table if pub_partopt is
+ * PUBLICATION_PART_LEAF.
+ *
+ * The second scan will get all the 'p'' relkind tables for the specified
+ * schema, iterate the 'p' relkind tables and prepare a list of:
+ * 1) partition table's child relations if pub_partopt is PUBLICATION_PART_LEAF
+ * 2) partition table if pub_partopt is PUBLICATION_PART_ROOT.
I think these comments are redundant and not sure if they are
completely correct. We don't need these as the actual code explains
these conditions better. The earlier part of these comments is
sufficient.
v37-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN
2.
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
+ * Publications have schemas, but those are ignored in decision making,
* because publications are only dumped when we are dumping everything.
*/
Change the above comment lines:
a. "Mark a publication as to be dumped or not" to "Mark a publication
object as to be dumped or not".
b. "Publications have schemas, but those are ignored in decision
making, .." to "A publication can have schemas and tables which have
schemas, but those are ignored in decision making, .."
3.
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication tables in schema mapping
+ */
Can we change the comment to: "dump the definition of the given
publication schema mapping"? IT is easier to read and understand.
4.
+/*
+ * The PublicationSchemaInfo struct is used to represent publication tables
+ * in schema mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ NamespaceInfo *pubschema;
+ PublicationInfo *publication;
+} PublicationSchemaInfo;
Can we change the comment similar to the comment change in point 3?
Also, let's keep PublicationInfo * before NamespaceInfo * just to be
consistent with the existing structure PublicationRelInfo?
5.
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND
pc.oid = '%s'\n"
I think this part of the query can be improved in multiple ways: (a)
pc.oid = '%s' should be part of WHERE clause not join condition, (b)
for pubname, no need to use alias name, it can be directly referred as
pubname, (c) you can check if the relation is publishable. So, the
formed query would look like:
SELECT p.pubname FROM pg_catalog.pg_publication p JOIN
pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid JOIN
pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid WHERE pc.oid =
'%s' and pg_catalog.pg_relation_is_publishable('%s')
6.
listSchemas()
{
..
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + Schema count + 1 (for
+ * storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
..
}
Is there a reason of not using printTableAddFooter() here similar to
how we use it to print Publications in describeOneTableDetails()?
7.
describePublications()
{
..
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n,\n"
+ " pg_catalog.pg_publication_namespace pn\n"
+ "WHERE n.oid = pn.pnnspid\n"
+ " AND pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
..
}
Shouldn't we try to get schemas only when pset.sversion >= 150000?
8.
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
..
+ termPQExpBuffer(buf);
..
}
It seems this buffer is freed at the caller's site, if so, no need to
free it here.
--
With Regards,
Amit Kapila.
On Thu, Oct 7, 2021 at 5:19 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Oct 6, 2021 at 11:12 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v37 patch has the changes for the same.
Few comments:
==============
Few more comments:
====================
v37-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN
1.
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL",
"TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' "
+ "UNION SELECT 'WITH ('");
What is the need to display WITH here? It will be displayed after
Schema name with the below rule:
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL",
"TABLES", "IN", "SCHEMA", MatchAny))
+ COMPLETE_WITH("WITH (");
2. It seems tab-completion happens incorrectly for the below case:
create publication pub for all tables in schema s1,
If I press the tab after above, it completes with below which is wrong
because it will lead to incorrect syntax.
create publication pub for all tables in schema s1, WITH (
v37-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication
3.
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
..
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
In this test, won't it exclude the schema dump_test because of unlike?
If so, then we don't have coverage for "ALL Tables In Schema" except
for public schema?
4.
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
..
+-- fail - can't add schema to for all tables publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
In the above and all similar comments, it is better to either quote
'for all tables' or write in CAPS FOR ALL TABLE or both 'FOR ALL
TABLE'.
5.
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes
| Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t
| t | f
+Tables from schemas:
+ "pub_test1"
+
+SELECT p.pubname FROM pg_catalog.pg_publication p,
pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn
WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname =
'pub_test1' ORDER BY 1;
+ pubname
+--------------------
+ testpub1_forschema
+(1 row)
I don't think in the above and similar tests, we need to separately
check the presence of publication via Select query, if we have tested
it via psql command. Let's try to keep the meaningful tests.
6.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica
identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica
identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica
identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
I think here we don't need to test both SET and ADD variants, one of
them is sufficient.
7.
+-- verify invalidation of partition table having partition on different schema
I think this comment is not very clear to me. Can we change it to:
"verify invalidation of partition table having parent and child tables
in different schema"?
8.
+-- verify invalidation of schema having partition parent table and
partition child table
Similarly, let's change this to: "verify invalidation of partition
tables for schema publication that has parent and child tables of
different partition hierarchies". Keep comments line boundary as 80
chars, that way they look readable.
9.
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
..
+# Basic logical replication test
Let's change this comment to: "Logical replication tests for schema
publications"
--
With Regards,
Amit Kapila.
On Friday, October 8, 2021 7:05 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
v37-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication 3. --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl .. + 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => { + create_order => 51, + create_sql => + 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;', + regexp => qr/^ + \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E + /xm, + like => { %full_runs, section_post_data => 1, }, + unlike => { exclude_dump_test_schema => 1, },In this test, won't it exclude the schema dump_test because of unlike?
If so, then we don't have coverage for "ALL Tables In Schema" except
for public schema?
Yes, the unlike case will exclude the schema dump_test, but I think schema dump_test could be
dumped in like case.
I checked the log file src/bin/pg_dump/tmp_check/log/regress_log_002_pg_dump and
saw some cases were described as "should dump ALTER PUBLICATION pub3 ADD ALL
TABLES IN SCHEMA dump_test". I think in these cases schema dump_test would be
dumped.
Besides, a small comment on tab-complete.c:
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
->
COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
Regards
Tang
On Thu, Oct 7, 2021 at 12:51 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Wed, Oct 6, 2021 at 4:42 PM vignesh C <vignesh21@gmail.com> wrote:
Attached v37 patch has the changes for the same.
A small issue I noticed is that using "\dS" in PSQL shows UNLOGGED
tables as belonging to a publication, if the table belongs to a schema
that was added to the publication using ALL TABLES IN SCHEMA (yet
doesn't show as part of an ALL TABLES publication).
Since publication of UNLOGGED tables is silently skipped in the case
of ALL TABLES and ALL TABLES IN SCHEMA, it shouldn't show as belonging
to the publication, right?test=# \dRp+ pub2
Publication pub2
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | t | t | t | t | t | f
(1 row)test=# \dRp+ pub
Publication pub
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | t
Tables from schemas:
"sch1"test=# \dS sch1.test2
Unlogged table "sch1.test2"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
i | integer | | |
Publications:
"pub"
Thanks for reporting the issue. The attached patch has the fix for the issue.
Regards,
Vignesh
Attachments:
v38-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v38-0001-Added-schema-level-support-for-publication.patchDownload
From e13b6b9bd83bf5ca0a85d83a2b61b6f3ca2e81f5 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 7 Oct 2021 19:26:38 +0530
Subject: [PATCH v38 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
Author: Vignesh C, Amit Kapila, Hou Zhijie
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 303 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 514 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 21 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1350 insertions(+), 188 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..04be11466d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,88 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +535,176 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ *
+ * Schema will be having both ordinary('r') relkind tables and partitioned('p')
+ * relkind tables, so two rounds of scan are required.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm) &&
+ !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ {
+ List *partitionrels = NIL;
+
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +832,26 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..55079a604d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,96 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +321,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +573,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +709,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +809,86 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +902,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +926,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +959,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +980,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +999,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1007,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1019,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1059,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1081,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c2ebe1bf6..e973cd3dd4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15960,6 +15961,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,12 +4810,15 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -4827,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2296,21 +2296,13 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
static bool
_equalCreatePublicationStmt(const CreatePublicationStmt *a,
const CreatePublicationStmt *b)
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..80e8bd0aba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17115,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17302,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..a4c894ec9d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-extern List *GetPubPartitionOptionRelations(List *result,
- PublicationPartOpt pub_partopt,
- Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern List *GetPubPartitionOptionRelations(List* result,
+ PublicationPartOpt pub_partopt,
+ Oid relid);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v38-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v38-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From e7e50bce687bdb745cc5b58f67857abf0f597fb5 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v38 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 198 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 34 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 352 insertions(+), 54 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..945cbcbd79 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10612,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18848,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0ef..37ac6b9dae 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5044,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5053,17 +5078,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6298,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6348,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6413,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6449,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6459,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6487,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..2a0f234622 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,28 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v38-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v38-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 033df3eaa64e121c51d18758eff7f87308aab9ca Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v38 3/5] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 514 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 256 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
8 files changed, 998 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..b4dd0f5444 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..e574dce747 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +388,446 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200adcb..433388ee64 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..704c707ec9 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +200,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +226,227 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v38-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v38-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 1b14500f639603406baaa4cd364f79cfe593a3f6 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v38 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 88 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 ++++++++++++++++++--
3 files changed, 207 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..92de24f6de 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6238,6 +6243,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..981e38189f 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +117,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +170,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..938237ae05 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v38-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v38-0005-Implemented-pg_publication_objects-view.patchDownload
From fbb0745cb87d14a5ebfb54957118f107e12bbd2e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v38 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 92de24f6de..a078302ac8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Thu, Oct 7, 2021 at 5:19 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Oct 6, 2021 at 11:12 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v37 patch has the changes for the same.
Few comments: ============== v37-0001-Added-schema-level-support-for-publication 1. + * + * The first scan will get all the 'r' relkind tables for the specified schema, + * iterate the 'r' relkind tables and prepare a list of: + * 1) non partition table if pub_partopt is PUBLICATION_PART_ROOT + * 2) partition table and non partition table if pub_partopt is + * PUBLICATION_PART_LEAF. + * + * The second scan will get all the 'p'' relkind tables for the specified + * schema, iterate the 'p' relkind tables and prepare a list of: + * 1) partition table's child relations if pub_partopt is PUBLICATION_PART_LEAF + * 2) partition table if pub_partopt is PUBLICATION_PART_ROOT.I think these comments are redundant and not sure if they are
completely correct. We don't need these as the actual code explains
these conditions better. The earlier part of these comments is
sufficient.
Removed it.
v37-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN 2. + * selectDumpablePublicationObject: policy-setting subroutine + * Mark a publication as to be dumped or not * - * Publication tables have schemas, but those are ignored in decision making, + * Publications have schemas, but those are ignored in decision making, * because publications are only dumped when we are dumping everything. */Change the above comment lines:
a. "Mark a publication as to be dumped or not" to "Mark a publication
object as to be dumped or not".b. "Publications have schemas, but those are ignored in decision
making, .." to "A publication can have schemas and tables which have
schemas, but those are ignored in decision making, .."
Modified
3. +/* + * dumpPublicationNamespace + * dump the definition of the given publication tables in schema mapping + */Can we change the comment to: "dump the definition of the given
publication schema mapping"? IT is easier to read and understand.4. +/* + * The PublicationSchemaInfo struct is used to represent publication tables + * in schema mapping. + */ +typedef struct _PublicationSchemaInfo +{ + DumpableObject dobj; + NamespaceInfo *pubschema; + PublicationInfo *publication; +} PublicationSchemaInfo;Can we change the comment similar to the comment change in point 3?
Also, let's keep PublicationInfo * before NamespaceInfo * just to be
consistent with the existing structure PublicationRelInfo?5. + printfPQExpBuffer(&buf, + "SELECT p.pubname\n" + "FROM pg_catalog.pg_publication p\n" + " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n" + " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid AND pc.oid = '%s'\n"I think this part of the query can be improved in multiple ways: (a)
pc.oid = '%s' should be part of WHERE clause not join condition, (b)
for pubname, no need to use alias name, it can be directly referred as
pubname, (c) you can check if the relation is publishable. So, the
formed query would look like:SELECT p.pubname FROM pg_catalog.pg_publication p JOIN
pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid JOIN
pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid WHERE pc.oid =
'%s' and pg_catalog.pg_relation_is_publishable('%s')
Modified
6. listSchemas() { .. + if (pub_schema_tuples > 0) + { + /* + * Allocate memory for footers. Size of footers will be 1 (for + * storing "Publications:" string) + Schema count + 1 (for + * storing NULL). + */ + footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *)); + footers[0] = pstrdup(_("Publications:")); + + /* Might be an empty set - that's ok */ + for (i = 0; i < pub_schema_tuples; i++) + { + printfPQExpBuffer(&buf, " \"%s\"", + PQgetvalue(result, i, 0)); + + footers[i + 1] = pstrdup(buf.data); + } + + footers[i + 1] = NULL; + myopt.footers = footers; + } .. }Is there a reason of not using printTableAddFooter() here similar to
how we use it to print Publications in describeOneTableDetails()?
There are 2 ways to print table in psql:
1) call printTableInit, printTableAddHeader, printTableAddCell,
printTableAddFooter, printTable & printTableCleanup to print the table
2) prepare the table contents and call printQuery to print the
table(which will take care of handling all of the above)
describeOneTableDetails uses 1st method
listSchemas uses 2nd method, in case of this method since table is not
initialized we cannot use printTableAddFooter. we have to prepare the
footers and set the footers.
7. describePublications() { .. + /* Get the schemas for the specified publication */ + printfPQExpBuffer(&buf, + "SELECT n.nspname\n" + "FROM pg_catalog.pg_namespace n,\n" + " pg_catalog.pg_publication_namespace pn\n" + "WHERE n.oid = pn.pnnspid\n" + " AND pn.pnpubid = '%s'\n" + "ORDER BY 1", pubid); + if (!addFooterToPublicationDesc(&buf, "Tables from schemas:", + true, &cont)) + goto error_return; .. }Shouldn't we try to get schemas only when pset.sversion >= 150000?
Modified
8. +addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg, + bool singlecol, printTableContent *cont) +{ .. + termPQExpBuffer(buf); .. }It seems this buffer is freed at the caller's site, if so, no need to
free it here.
Modified
These comments are fixed in the v38 patch attached.
Regards,
Vignesh
Attachments:
v38-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v38-0001-Added-schema-level-support-for-publication.patchDownload
From e13b6b9bd83bf5ca0a85d83a2b61b6f3ca2e81f5 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 7 Oct 2021 19:26:38 +0530
Subject: [PATCH v38 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
Author: Vignesh C, Amit Kapila, Hou Zhijie
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 303 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 514 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 21 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1350 insertions(+), 188 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..04be11466d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,88 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +535,176 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ *
+ * Schema will be having both ordinary('r') relkind tables and partitioned('p')
+ * relkind tables, so two rounds of scan are required.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm) &&
+ !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ {
+ List *partitionrels = NIL;
+
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +832,26 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..55079a604d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,96 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +321,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +573,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +709,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +809,86 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +902,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +926,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +959,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +980,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +999,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1007,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1019,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1059,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1081,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c2ebe1bf6..e973cd3dd4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15960,6 +15961,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,12 +4810,15 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -4827,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2296,21 +2296,13 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
static bool
_equalCreatePublicationStmt(const CreatePublicationStmt *a,
const CreatePublicationStmt *b)
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..80e8bd0aba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17115,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17302,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..a4c894ec9d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-extern List *GetPubPartitionOptionRelations(List *result,
- PublicationPartOpt pub_partopt,
- Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern List *GetPubPartitionOptionRelations(List* result,
+ PublicationPartOpt pub_partopt,
+ Oid relid);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v38-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v38-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 57f4accb82c9c8272af68fb80229aaa3b6769c16 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v38 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 198 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 34 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 352 insertions(+), 54 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..945cbcbd79 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10612,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18848,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0ef..c93478070d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5044,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5053,17 +5078,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6298,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6348,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6413,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6449,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6459,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6487,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..2a0f234622 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,28 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v38-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v38-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 5ff33b318b26da2fe03508f70cfc0dcf196ff201 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v38 3/5] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 514 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 256 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
8 files changed, 998 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..b4dd0f5444 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..e574dce747 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +388,446 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200adcb..433388ee64 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..704c707ec9 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +200,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +226,227 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v38-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v38-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 1fc390c04f49fa46ee3cfbcb619e0fd009a38bdf Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v38 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 88 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 73 ++++++++++++++++++--
3 files changed, 207 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..92de24f6de 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6238,6 +6243,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..981e38189f 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,24 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +117,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -141,6 +170,33 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Drop some schemas from the publication:
+<programlisting>
+ALTER PUBLICATION production_quarterly_publication DROP ALL TABLES IN SCHEMA production_july, production_august;
+</programlisting>
+ </para>
+
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..938237ae05 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,31 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v38-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v38-0005-Implemented-pg_publication_objects-view.patchDownload
From 3b905f6d595466fc249246845b9dad1eed7aae17 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v38 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 92de24f6de..a078302ac8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Fri, Oct 8, 2021 at 4:34 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Oct 7, 2021 at 5:19 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Oct 6, 2021 at 11:12 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v37 patch has the changes for the same.
Few comments:
==============Few more comments: ==================== v37-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN 1. + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA")) + COMPLETE_WITH_QUERY(Query_for_list_of_schemas + " UNION SELECT 'CURRENT_SCHEMA' " + "UNION SELECT 'WITH ('");What is the need to display WITH here? It will be displayed after Schema name with the below rule: + else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny)) + COMPLETE_WITH("WITH (");
Removed it.
2. It seems tab-completion happens incorrectly for the below case:
create publication pub for all tables in schema s1,If I press the tab after above, it completes with below which is wrong
because it will lead to incorrect syntax.create publication pub for all tables in schema s1, WITH (
Modified
v37-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication 3. --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl .. + 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => { + create_order => 51, + create_sql => + 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;', + regexp => qr/^ + \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E + /xm, + like => { %full_runs, section_post_data => 1, }, + unlike => { exclude_dump_test_schema => 1, },In this test, won't it exclude the schema dump_test because of unlike?
If so, then we don't have coverage for "ALL Tables In Schema" except
for public schema?
I noticed that the tap test framework runs pg_dump tests with various
combinations, these tests will be covered in the like tests as Tang
suggested at [1]/messages/by-id/OS0PR01MB6113A715FB3B85907458F4E7FBB59@OS0PR01MB6113.jpnprd01.prod.outlook.com. In the exclude_dump_test_schema since this sql will
not be present, exclude_dump_test_schema should be added in the
unlike option, as the validation will fail for exclude-schema option
which will be run with the following command:
pg_dump --no-sync
--file=/home/vignesh/postgres/src/bin/pg_dump/tmp_check/tmp_test_4i8F/exclude_dump_test_schema.sql
--exclude-schema=dump_test postgres
4. --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out .. +-- fail - can't add schema to for all tables publication +ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;In the above and all similar comments, it is better to either quote
'for all tables' or write in CAPS FOR ALL TABLE or both 'FOR ALL
TABLE'.
Modified
5. +\dRp+ testpub1_forschema + Publication testpub1_forschema + Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+---------+---------+---------+-----------+---------- + regress_publication_user | f | t | t | t | t | f +Tables from schemas: + "pub_test1" + +SELECT p.pubname FROM pg_catalog.pg_publication p, pg_catalog.pg_namespace n, pg_catalog.pg_publication_namespace pn WHERE n.oid = pn.pnnspid AND p.oid = pn.pnpubid AND n.nspname = 'pub_test1' ORDER BY 1; + pubname +-------------------- + testpub1_forschema +(1 row)I don't think in the above and similar tests, we need to separately
check the presence of publication via Select query, if we have tested
it via psql command. Let's try to keep the meaningful tests.
Removed it.
6. +INSERT INTO pub_test1.tbl VALUES(1, 'test'); +-- fail +UPDATE pub_test1.tbl SET id = 2; +ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates +HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE. +ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1; +-- success +UPDATE pub_test1.tbl SET id = 2; +ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1; +-- fail +UPDATE pub_test1.tbl SET id = 2; +ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates +HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE. +ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1; +-- success +UPDATE pub_test1.tbl SET id = 2; +ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1; +-- fail +UPDATE pub_test1.tbl SET id = 2; +ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates +HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.I think here we don't need to test both SET and ADD variants, one of
them is sufficient.
Removed it
7.
+-- verify invalidation of partition table having partition on different schemaI think this comment is not very clear to me. Can we change it to:
"verify invalidation of partition table having parent and child tables
in different schema"?
Modified
8.
+-- verify invalidation of schema having partition parent table and
partition child tableSimilarly, let's change this to: "verify invalidation of partition
tables for schema publication that has parent and child tables of
different partition hierarchies". Keep comments line boundary as 80
chars, that way they look readable.
Modified
9. +++ b/src/test/subscription/t/025_rep_changes_for_schema.pl .. +# Basic logical replication testLet's change this comment to: "Logical replication tests for schema
publications"
Modified
These comments are handled in the v38 patch attached at [2]/messages/by-id/CALDaNm1TP9S0dif2QWoEUcCtNDop1xJ6Rj1xnu2vS92=j9ahYw@mail.gmail.com.
[1]: /messages/by-id/OS0PR01MB6113A715FB3B85907458F4E7FBB59@OS0PR01MB6113.jpnprd01.prod.outlook.com
[2]: /messages/by-id/CALDaNm1TP9S0dif2QWoEUcCtNDop1xJ6Rj1xnu2vS92=j9ahYw@mail.gmail.com
Regards,
Vignesh
On Mon, Oct 11, 2021 at 7:46 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Friday, October 8, 2021 7:05 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
v37-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication 3. --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl .. + 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => { + create_order => 51, + create_sql => + 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;', + regexp => qr/^ + \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E + /xm, + like => { %full_runs, section_post_data => 1, }, + unlike => { exclude_dump_test_schema => 1, },In this test, won't it exclude the schema dump_test because of unlike?
If so, then we don't have coverage for "ALL Tables In Schema" except
for public schema?Yes, the unlike case will exclude the schema dump_test, but I think schema dump_test could be
dumped in like case.
I checked the log file src/bin/pg_dump/tmp_check/log/regress_log_002_pg_dump and
saw some cases were described as "should dump ALTER PUBLICATION pub3 ADD ALL
TABLES IN SCHEMA dump_test". I think in these cases schema dump_test would be
dumped.
I agree
Besides, a small comment on tab-complete.c:
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL")) - COMPLETE_WITH("TABLES"); - else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES") - || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny)) + COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");COMPLETE_WITH("TABLES", "TABLE IN SCHEMA");
->
COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
Modified.
This issue is fixed in the v38 patch attached at [1]/messages/by-id/CALDaNm1TP9S0dif2QWoEUcCtNDop1xJ6Rj1xnu2vS92=j9ahYw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm1TP9S0dif2QWoEUcCtNDop1xJ6Rj1xnu2vS92=j9ahYw@mail.gmail.com
Regards,
Vignesh
On Monday, October 11, 2021 2:39 PM vignesh C <vignesh21@gmail.com> wrote:
These comments are fixed in the v38 patch attached.
Thanks for updating the patches.
Here are a few comments on the v38-0004-Doc patch.
1.
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication along with same schema's table specified with
+ <literal>TABLE</literal>, adding/setting a schema to a publication that
+ already has a table that is part of specified schema or adding/setting a
+ table to a publication that already has a table's schema as part of
+ specified schema is not supported.
ISTM we can remove the description "adding/setting a schema to a publication
along with same schema's table specified with <literal>TABLE</literal>",
because it seems the same as the first mentioned case "Adding/Setting a table
that is part of schema specified in <literal>ALL TABLES IN SCHEMA</literal>"
2.
+</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
...
+
+ <para>
+ Drop some schemas from the publication:
...
+ <para>
+ Set some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;
Personally, I think we don't need the example about DROP and SET here.
The example of ADD seems sufficient.
3.
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
...
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
I think the example for publishing all the tables in schemas "marketing" and
"sales" is sufficient, the example for pulishing signal schema seems can be
removed.
Best regards,
Hou zj
On Mon, Oct 11, 2021 at 5:39 PM vignesh C <vignesh21@gmail.com> wrote:
These comments are fixed in the v38 patch attached.
Thanks for the updates.
I noticed that these patches don't apply on the latest source (last
seemed to apply cleanly on HEAD as at about October 6).
Regards,
Greg Nancarrow
Fujitsu Australia
On Mon, Oct 11, 2021 at 1:21 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Mon, Oct 11, 2021 at 5:39 PM vignesh C <vignesh21@gmail.com> wrote:
These comments are fixed in the v38 patch attached.
Thanks for the updates.
I noticed that these patches don't apply on the latest source (last
seemed to apply cleanly on HEAD as at about October 6).
I was not able to apply v37 patches, but I was able to apply the v38
version of patches on top of HEAD.
Regards,
Vignesh
On Mon, Oct 11, 2021 at 12:50 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Monday, October 11, 2021 2:39 PM vignesh C <vignesh21@gmail.com> wrote:
These comments are fixed in the v38 patch attached.
Thanks for updating the patches.
Here are a few comments on the v38-0004-Doc patch.1. + <para> + Adding/Setting a table that is part of schema specified in + <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a + publication along with same schema's table specified with + <literal>TABLE</literal>, adding/setting a schema to a publication that + already has a table that is part of specified schema or adding/setting a + table to a publication that already has a table's schema as part of + specified schema is not supported.ISTM we can remove the description "adding/setting a schema to a publication
along with same schema's table specified with <literal>TABLE</literal>",
because it seems the same as the first mentioned case "Adding/Setting a table
that is part of schema specified in <literal>ALL TABLES IN SCHEMA</literal>"
Modified
2.
+</programlisting></para> + + <para> + Add some schemas to the publication: +<programlisting> +ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june; +</programlisting> + </para> + + <para> + Add some tables and schemas to the publication: ... + + <para> + Drop some schemas from the publication: ... + <para> + Set some schemas to the publication: +<programlisting> +ALTER PUBLICATION production_publication SET ALL TABLES IN SCHEMA production_september, production_october;Personally, I think we don't need the example about DROP and SET here.
The example of ADD seems sufficient.
Modified
3. +</programlisting> + </para> + + <para> + Create a publication that publishes all changes for all the tables present in + the schema "production": +<programlisting> +CREATE PUBLICATION production_publication FOR ALL TABLES IN SCHEMA production; +</programlisting> + </para> ... + <para> + Create a publication that publishes all changes for all the tables present in + the schemas "marketing" and "sales": +<programlisting> +CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;I think the example for publishing all the tables in schemas "marketing" and
"sales" is sufficient, the example for pulishing signal schema seems can be
removed.
Modified
The attached v39 patch has the fixes for the above issues.
Regards,
Vignesh
Attachments:
v39-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v39-0001-Added-schema-level-support-for-publication.patchDownload
From 02c995724f2e7096984fc648c80e9d241f88519b Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 7 Oct 2021 19:26:38 +0530
Subject: [PATCH v39 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
Author: Vignesh C, Amit Kapila, Hou Zhijie
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 303 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 514 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 21 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1350 insertions(+), 188 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..04be11466d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,88 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +535,176 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ *
+ * Schema will be having both ordinary('r') relkind tables and partitioned('p')
+ * relkind tables, so two rounds of scan are required.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm) &&
+ !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ {
+ List *partitionrels = NIL;
+
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +832,26 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..55079a604d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,96 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +321,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +573,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +709,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +809,86 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +902,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +926,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +959,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +980,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +999,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1007,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1019,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1059,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1081,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c2ebe1bf6..e973cd3dd4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15960,6 +15961,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,12 +4810,15 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -4827,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2296,21 +2296,13 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
static bool
_equalCreatePublicationStmt(const CreatePublicationStmt *a,
const CreatePublicationStmt *b)
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..80e8bd0aba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17115,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17302,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..a4c894ec9d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-extern List *GetPubPartitionOptionRelations(List *result,
- PublicationPartOpt pub_partopt,
- Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern List *GetPubPartitionOptionRelations(List* result,
+ PublicationPartOpt pub_partopt,
+ Oid relid);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v39-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v39-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From b89acdecc0a6cf81040af4886c46e0cd1e5a3c0e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v39 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 198 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 34 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 352 insertions(+), 54 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..945cbcbd79 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10612,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18848,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0ef..c93478070d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5044,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5053,17 +5078,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6298,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6348,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6413,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6449,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6459,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6487,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..2a0f234622 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,28 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v39-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v39-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 5a1c86d2d8edf1a0d83d150a2ba76bbf278fe4c9 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v39 3/5] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 514 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 256 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
8 files changed, 998 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..b4dd0f5444 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..e574dce747 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +388,446 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "public"
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | t | t | t | t | f
+Tables from schemas:
+ "public"
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200adcb..433388ee64 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..704c707ec9 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +200,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +226,227 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication add CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set it with CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v39-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v39-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 4ee443279a89e24e58806f7bb5c8fecafdcbf862 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v39 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 73 ++++++++++++++++++------
doc/src/sgml/ref/create_publication.sgml | 65 ++++++++++++++++++---
3 files changed, 184 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..92de24f6de 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6238,6 +6243,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..f8a79eb7c1 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +115,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +169,20 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..f4d9a73dbf 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,23 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v39-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v39-0005-Implemented-pg_publication_objects-view.patchDownload
From 06eea5deb070132128add5ef809835214eb1634d Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v39 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 92de24f6de..a078302ac8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Monday, October 11, 2021 11:02 PM vignesh C <vignesh21@gmail.com> wrote:
The attached v39 patch has the fixes for the above issues.
Thanks for the updates.
I have a few minor suggestions about the testcases in the v39-0003-Test patch.
1)
+-- alter publication drop CURRENT_SCHEMA
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+\dRp+ testpub1_forschema
Since we already tested CURRENT_SCHEMA in various CREATE PUBLICATION cases, maybe
we don't need to test it again in SET/DROP/ADD cases.
2)
+-- alter publication set schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
I think the multiple schemas testcase is sufficient, maybe we can remove the
single schema case.
3)
+
+-- alter publication set it with the same schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
ISTM, we didn't have some special code path for this case, maybe we can remove
this testcase.
Best regards,
Hou zj
On Tue, Oct 12, 2021 at 12:24 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Monday, October 11, 2021 11:02 PM vignesh C <vignesh21@gmail.com> wrote:
The attached v39 patch has the fixes for the above issues.
Thanks for the updates.
I have a few minor suggestions about the testcases in the v39-0003-Test patch.1) +-- alter publication drop CURRENT_SCHEMA +ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA CURRENT_SCHEMA; +\dRp+ testpub1_forschemaSince we already tested CURRENT_SCHEMA in various CREATE PUBLICATION cases, maybe
we don't need to test it again in SET/DROP/ADD cases.
Modified
2) +-- alter publication set schema +ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1; +\dRp+ testpub1_forschema + +-- alter publication set multiple schema +ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2; +\dRp+ testpub1_forschema +I think the multiple schemas testcase is sufficient, maybe we can remove the
single schema case.
Modified
3) + +-- alter publication set it with the same schema +ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2; +\dRp+ testpub1_forschemaISTM, we didn't have some special code path for this case, maybe we can remove
this testcase.
Modified
Attached v40 patch has the fix for the above comments.
Regards,
Vignesh
Attachments:
v40-0001-Added-schema-level-support-for-publication.patchtext/x-patch; charset=US-ASCII; name=v40-0001-Added-schema-level-support-for-publication.patchDownload
From 6ccd0586ccf33f4771d50b0a55691c91fccd93ff Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 7 Oct 2021 19:26:38 +0530
Subject: [PATCH v40 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
Author: Vignesh C, Amit Kapila, Hou Zhijie
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 303 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 514 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 21 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1350 insertions(+), 188 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..04be11466d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,88 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +535,176 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ *
+ * Schema will be having both ordinary('r') relkind tables and partitioned('p')
+ * relkind tables, so two rounds of scan are required.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm) &&
+ !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ {
+ List *partitionrels = NIL;
+
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +832,26 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..55079a604d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,96 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +321,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +573,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +709,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +809,86 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +902,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +926,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +959,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +980,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +999,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1007,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1019,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1059,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1081,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c2ebe1bf6..e973cd3dd4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15960,6 +15961,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,12 +4810,15 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -4827,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2296,21 +2296,13 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
static bool
_equalCreatePublicationStmt(const CreatePublicationStmt *a,
const CreatePublicationStmt *b)
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..80e8bd0aba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17115,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17302,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..a4c894ec9d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-extern List *GetPubPartitionOptionRelations(List *result,
- PublicationPartOpt pub_partopt,
- Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern List *GetPubPartitionOptionRelations(List* result,
+ PublicationPartOpt pub_partopt,
+ Oid relid);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v40-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchtext/x-patch; charset=US-ASCII; name=v40-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 5470d932e083e6c6bc14c954e75d84b64340964e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v40 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 198 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 34 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 352 insertions(+), 54 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..945cbcbd79 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10612,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18848,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0ef..c93478070d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5044,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5053,17 +5078,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6298,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6348,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6413,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6449,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6459,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6487,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..2a0f234622 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,28 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v40-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchtext/x-patch; charset=US-ASCII; name=v40-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 177bb1a419e6f6c8cfc6690796a6327bfa24481b Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v40 3/5] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 458 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 236 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 +++++++
8 files changed, 922 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..b4dd0f5444 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..459371b11e 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +388,390 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200adcb..433388ee64 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..3ef2f68eec 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +200,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +226,207 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v40-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchtext/x-patch; charset=US-ASCII; name=v40-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 840a5c3bea6cd627d604ab057ee98b6dfee168a1 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v40 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 73 ++++++++++++++++++------
doc/src/sgml/ref/create_publication.sgml | 65 ++++++++++++++++++---
3 files changed, 184 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..92de24f6de 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6238,6 +6243,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..f8a79eb7c1 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +115,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +169,20 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..f4d9a73dbf 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,23 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v40-0005-Implemented-pg_publication_objects-view.patchtext/x-patch; charset=US-ASCII; name=v40-0005-Implemented-pg_publication_objects-view.patchDownload
From f2c80a8313a588cd6cfa7f87ca4834037365e030 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v40 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 92de24f6de..a078302ac8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Tuesday, October 12, 2021 9:15 PM vignesh C <vignesh21@gmail.com>
Attached v40 patch has the fix for the above comments.
Thanks for the update, I have some minor issues about partition related behavior.
1)
Tang tested and discussed this issue with me.
The testcase is:
We publish a schema and there is a partition in the published schema. If
publish_via_partition_root is on and the partition's parent table is not in the
published schema, neither the change on the partition nor the parent table will
not be published.
But if we publish by FOR TABLE partition and set publish_via_partition_root to
on, the change on the partition will be published. So, I think it'd be better to
publish the change on partition for FOR ALL TABLES IN SCHEMA case if its parent table
is not published in the same publication.
It seems we should pass publication oid to the GetSchemaPublicationRelations()
and add some check like the following:
if (is_publishable_class(relid, relForm) &&
!(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
result = lappend_oid(result, relid);
+ if (relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT)
+ {
+ bool skip = false;
+ List *ancestors = get_partition_ancestors(relid);
+ List *schemas = GetPublicationSchemas(pubid);
+ ListCell *lc;
+ foreach(lc, ancestors)
+ {
+ if (list_member_oid(schemas, get_rel_namespace(lfirst_oid(lc))))
+ {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip)
+ result = lappend_oid(result, relid);
+ }
2)
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ {
+ List *partitionrels = NIL;
+
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
I think a partitioned table could also be a partition which should not be
appended to the list. I think we should also filter these cases here by same
check in 1).
Thoughts ?
Best regards,
Hou zj
On Wed, Oct 13, 2021 at 12:15 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v40 patch has the fix for the above comments.
[Maybe this has some overlap with what Hou-san reported, and I have
not tested this against his proposed fixes]
If partitions belong to a different schema than the parent partitioned
table, then the current patch implementation allows the partitions to
(optionally) be explicitly added to a publication that includes the
parent partitioned table (and for the most part, it doesn't seem to
make any difference to the publication behavior). Should this be
allowed?
e.g.
CREATE SCHEMA sch;
CREATE SCHEMA sch1;
CREATE TABLE sch.sale (sale_date date not null, country_code text,
product_sku text, units integer) PARTITION BY RANGE (sale_date);
CREATE TABLE sch1.sale_201901 PARTITION OF sch.sale FOR VALUES FROM
('2019-01-01') TO ('2019-02-01');
CREATE TABLE sch1.sale_201902 PARTITION OF sch.sale FOR VALUES FROM
('2019-02-01') TO ('2019-03-01');
postgres=# CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch, TABLE
sch1.sale_201901, TABLE sch1.sale_201902;
CREATE PUBLICATION
postgres=# \dRp+
Publication pub
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Tables:
"sch1.sale_201901"
"sch1.sale_201902"
Tables from schemas:
"sch"
Also, I found the following scenario where the data is double-published:
(1) PUB: CREATE PUBLICATION pub FOR TABLE sch1.sale_201901, TABLE
sch1.sale_201902 WITH (publish_via_partition_root=true);
(2) SUB: CREATE SUBSCRIPTION sub CONNECTION 'dbname=postgres
host=localhost port=5432' PUBLICATION pub;
(3) PUB: INSERT INTO sch.sale VALUES('2019-01-01', 'AU', 'cpu', 5),
('2019-01-02', 'AU', 'disk', 8);
(4) SUB: SELECT * FROM sch.sale;
(5) PUB: ALTER PUBLICATION pub ADD ALL TABLES IN SCHEMA sch;
(6) SUB: ALTER SUBSCRIPTION sub REFRESH PUBLICATION;
(7) SUB: SELECT * FROM sch.sale;
sale_date | country_code | product_sku | units
------------+--------------+-------------+-------
2019-01-01 | AU | cpu | 5
2019-01-02 | AU | disk | 8
2019-01-01 | AU | cpu | 5
2019-01-02 | AU | disk | 8
Regards,
Greg Nancarrow
Fujitsu Australia
On Wednesday, October 13, 2021 4:10 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
Also, I found the following scenario where the data is double-published:
(1) PUB: CREATE PUBLICATION pub FOR TABLE sch1.sale_201901, TABLE
sch1.sale_201902 WITH (publish_via_partition_root=true);
(2) SUB: CREATE SUBSCRIPTION sub CONNECTION 'dbname=postgres
host=localhost port=5432' PUBLICATION pub;
(3) PUB: INSERT INTO sch.sale VALUES('2019-01-01', 'AU', 'cpu', 5),
('2019-01-02', 'AU', 'disk', 8);
(4) SUB: SELECT * FROM sch.sale;
(5) PUB: ALTER PUBLICATION pub ADD ALL TABLES IN SCHEMA sch;
(6) SUB: ALTER SUBSCRIPTION sub REFRESH PUBLICATION;
(7) SUB: SELECT * FROM sch.sale;sale_date | country_code | product_sku | units
------------+--------------+-------------+-------
2019-01-01 | AU | cpu | 5
2019-01-02 | AU | disk | 8
2019-01-01 | AU | cpu | 5
2019-01-02 | AU | disk | 8
I changed your test step in (5) and used "ADD TABLE" command as below:
ALTER PUBLICATION pub ADD TABLE sch.sale;
I could get the same result on HEAD.
So I think it's not a problem related to this patch.
Maybe we can post this issue in a new thread.
Regards
Tang
On Wed, Oct 13, 2021 at 1:40 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Wed, Oct 13, 2021 at 12:15 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v40 patch has the fix for the above comments.
[Maybe this has some overlap with what Hou-san reported, and I have
not tested this against his proposed fixes]If partitions belong to a different schema than the parent partitioned
table, then the current patch implementation allows the partitions to
(optionally) be explicitly added to a publication that includes the
parent partitioned table (and for the most part, it doesn't seem to
make any difference to the publication behavior). Should this be
allowed?e.g.
CREATE SCHEMA sch;
CREATE SCHEMA sch1;
CREATE TABLE sch.sale (sale_date date not null, country_code text,
product_sku text, units integer) PARTITION BY RANGE (sale_date);
CREATE TABLE sch1.sale_201901 PARTITION OF sch.sale FOR VALUES FROM
('2019-01-01') TO ('2019-02-01');
CREATE TABLE sch1.sale_201902 PARTITION OF sch.sale FOR VALUES FROM
('2019-02-01') TO ('2019-03-01');postgres=# CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch, TABLE
sch1.sale_201901, TABLE sch1.sale_201902;
CREATE PUBLICATION
postgres=# \dRp+
Publication pub
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Tables:
"sch1.sale_201901"
"sch1.sale_201902"
Tables from schemas:
"sch"
I don't see any problem with this. Do you have a specific problem in
mind due to this?
--
With Regards,
Amit Kapila.
On Thu, Oct 14, 2021 at 9:59 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
If partitions belong to a different schema than the parent partitioned
table, then the current patch implementation allows the partitions to
(optionally) be explicitly added to a publication that includes the
parent partitioned table (and for the most part, it doesn't seem to
make any difference to the publication behavior). Should this be
allowed?e.g.
CREATE SCHEMA sch;
CREATE SCHEMA sch1;
CREATE TABLE sch.sale (sale_date date not null, country_code text,
product_sku text, units integer) PARTITION BY RANGE (sale_date);
CREATE TABLE sch1.sale_201901 PARTITION OF sch.sale FOR VALUES FROM
('2019-01-01') TO ('2019-02-01');
CREATE TABLE sch1.sale_201902 PARTITION OF sch.sale FOR VALUES FROM
('2019-02-01') TO ('2019-03-01');postgres=# CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch, TABLE
sch1.sale_201901, TABLE sch1.sale_201902;
CREATE PUBLICATION
postgres=# \dRp+
Publication pub
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Tables:
"sch1.sale_201901"
"sch1.sale_201902"
Tables from schemas:
"sch"I don't see any problem with this. Do you have a specific problem in
mind due to this?
I'm not sure if it's a problem as such, really just a query from me as
to whether it should be allowed to also (redundantly) add partitions
to the publication, in addition to the partitioned table, since the
current documentation says: "When a partitioned table is added to a
publication, all of its existing and future partitions are implicitly
considered to be part of the publication".
I guess it should be allowed, as I find I can do it in the current
implementation just with TABLE.
Regards,
Greg Nancarrow
Fujitsu Australia
On Wednesday, October 13, 2021 10:49 AM Hou, Zhijie wrote:
On Tuesday, October 12, 2021 9:15 PM vignesh C <vignesh21@gmail.com>
Attached v40 patch has the fix for the above comments.
Thanks for the update, I have some minor issues about partition related
behavior.1)
Tang tested and discussed this issue with me.
The testcase is:
We publish a schema and there is a partition in the published schema. If
publish_via_partition_root is on and the partition's parent table is not in the
published schema, neither the change on the partition nor the parent table will
not be published.But if we publish by FOR TABLE partition and set publish_via_partition_root to
on, the change on the partition will be published. So, I think it'd be better to
publish the change on partition for FOR ALL TABLES IN SCHEMA case if its parent
table
is not published in the same publication.It seems we should pass publication oid to the GetSchemaPublicationRelations()
and add some check like the following:
2)
I think a partitioned table could also be a partition which should not be
appended to the list. I think we should also filter these cases here by same
check in 1).
After some offline discussion with Vignesh and Amit.
I found my proposed fix can be improved because it brings some overhead to
functions which could be invoked many times for the same publication. Now, I
think it'd be better to add this check in GetAllSchemaPublicationRelations and
GetPublicationRelations.
Besides, I found we misunderstood the flag PUBLICATION_PART_ROOT it means:
"ROOT: only the table explicitly mentioned in the publication" We cannot use it
as a flag to judge whether do the partition filtering, I think we need to pass
the actual pubviaroot flag.
Based on the V40 patchset, attaching the Top-up patch which try to fix the
partition issue in a cleaner way.
Best regards,
Hou zj
Attachments:
v40-Topup-partition-fix.patchapplication/octet-stream; name=v40-Topup-partition-fix.patchDownload
From 30c4b8a61a21e83b27251908f961395f183a7647 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Sat, 16 Oct 2021 12:32:13 +0800
Subject: [PATCH] Topup Hou
---
src/backend/catalog/pg_publication.c | 121 +++++++++++++++++++++++++++---
src/backend/commands/publicationcmds.c | 6 +-
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 59 +++++++++++++++
src/test/regress/sql/publication.sql | 41 ++++++++++
5 files changed, 217 insertions(+), 16 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 04be114..f6343f6 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -131,6 +131,59 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
}
/*
+ * Filter out the partitions whose parent tables was also specified in
+ * the publication.
+ */
+static List *
+filter_out_partitions(Oid pubid, List *relids,
+ PublicationObjSpecType reltype)
+{
+ List *result = NIL;
+ List *published_relids = NIL;
+ List *schemaids = NIL;
+ ListCell *lc;
+ ListCell *lc2;
+
+ if (reltype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ published_relids = GetPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
+ else if (reltype == PUBLICATIONOBJ_TABLE)
+ schemaids = GetPublicationSchemas(pubid);
+
+ foreach(lc, relids)
+ {
+ bool skip = false;
+ List *ancestors = NIL;
+ Oid relid = lfirst_oid(lc);
+
+ if (get_rel_relispartition(relid))
+ ancestors = get_partition_ancestors(relid);
+
+ foreach(lc2, ancestors)
+ {
+ /*
+ * Check if the parent table exists in the published table list or
+ * the schema of the parent table was published in this
+ * publication.
+ */
+ if (list_member_oid(relids, lfirst_oid(lc2)) ||
+ list_member_oid(published_relids, lfirst_oid(lc2)) ||
+ (schemaids != NIL &&
+ list_member_oid(schemaids, get_rel_namespace(lfirst_oid(lc2)))))
+ {
+ skip = true;
+ break;
+ }
+ }
+
+ if (!skip)
+ result = lappend_oid(result, relid);
+ }
+
+ return result;
+}
+
+/*
* Another variant of this, taking a Relation.
*/
bool
@@ -363,7 +416,8 @@ publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
* publication_add_relation for why we need to consider all the
* partitions.
*/
- schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ schemaRels = GetSchemaPublicationRelations(schemaid,
+ PUBLICATION_PART_ALL);
InvalidatePublicationRels(schemaRels);
return myself;
@@ -436,6 +490,34 @@ GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
+ * Gets list of relation explicitly mentioned in the publication.
+ *
+ * This should only be used FOR TABLE publications, the FOR ALL TABLES
+ * should use GetAllTablesPublicationRelations().
+ *
+ * If pubviaroot is true, we must exclude partitions in favor of including the
+ * root partitioned tables.
+ */
+List *
+GetPublicationRelationsExtended(Oid pubid, bool pubviaroot)
+{
+ List *result;
+
+ result = GetPublicationRelations(pubid, PUBLICATION_PART_ROOT);
+
+ /*
+ * Filter out the partitions whose parent table is also in the same
+ * publication.
+ */
+ if (pubviaroot)
+ return filter_out_partitions(pubid, result, PUBLICATIONOBJ_TABLE);
+ else
+ return result;
+}
+
+
+
+/*
* Gets list of publication oids for publications marked as FOR ALL TABLES.
*/
List *
@@ -608,7 +690,8 @@ GetSchemaPublications(Oid schemaid)
* relkind tables, so two rounds of scan are required.
*/
List *
-GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt)
{
Relation classRel;
ScanKeyData key[3];
@@ -638,8 +721,7 @@ GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
Oid relid = relForm->oid;
- if (is_publishable_class(relid, relForm) &&
- !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
+ if (is_publishable_class(relid, relForm))
result = lappend_oid(result, relid);
}
@@ -685,12 +767,16 @@ GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
/*
* Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
* publication.
+ *
+ * If pubviaroot is true, we must exclude partitions in favor of including the
+ * root partitioned tables.
*/
List *
-GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ bool pubviaroot)
{
List *result = NIL;
- List *pubschemalist = GetPublicationSchemas(puboid);
+ List *pubschemalist = GetPublicationSchemas(pubid);
ListCell *cell;
foreach(cell, pubschemalist)
@@ -702,7 +788,14 @@ GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
result = list_concat(result, schemaRels);
}
- return result;
+ /*
+ * Filter out the partitions whose parent table is also in the same
+ * publication.
+ */
+ if (pubviaroot)
+ return filter_out_partitions(pubid, result, PUBLICATIONOBJ_REL_IN_SCHEMA);
+ else
+ return result;
}
/*
@@ -840,14 +933,18 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
List *relids,
*schemarelids;
- relids = GetPublicationRelations(publication->oid,
- publication->pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ if (publication->pubviaroot)
+ relids = GetPublicationRelationsExtended(publication->oid,
+ publication->pubviaroot);
+ else
+ relids = GetPublicationRelations(publication->oid,
+ PUBLICATION_PART_LEAF);
+
schemarelids = GetAllSchemaPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ publication->pubviaroot);
tables = list_concat_unique_oid(relids, schemarelids);
}
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 55079a6..d867c4e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -452,7 +452,8 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
relids = GetPublicationRelations(pubform->oid,
PUBLICATION_PART_ALL);
schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ PUBLICATION_PART_ALL,
+ false);
relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
@@ -830,7 +831,8 @@ RemovePublicationSchemaById(Oid psoid)
/*
* Invalidate relcache so that publication info is rebuilt. See
- * RemovePublicationRelById for why we need to consider all the partitions.
+ * RemovePublicationRelById for why we need to consider all the
+ * partitions.
*/
schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
PUBLICATION_PART_ALL);
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index a4c894e..9672649 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -104,6 +104,7 @@ typedef enum PublicationPartOpt
} PublicationPartOpt;
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
+extern List *GetPublicationRelationsExtended(Oid pubid, bool pubviaroot);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -111,10 +112,11 @@ extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
PublicationPartOpt pub_partopt);
extern List *GetAllSchemaPublicationRelations(Oid puboid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ bool pubviaroot);
extern bool is_publishable_relation(Relation rel);
-extern List *GetPubPartitionOptionRelations(List* result,
+extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 459371b..d34c1ff 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -772,6 +772,65 @@ DETAIL: drop cascades to table pub_testpart1.parent1
drop cascades to table pub_testpart1.child_parent2
DROP SCHEMA pub_testpart2 CASCADE;
NOTICE: drop cascades to table pub_testpart2.parent2
+-- pg_publication_tables
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+-----------
+ pub | sch1 | tbl1
+(1 row)
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+ERROR: table "tbl1_part1" does not exist
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 3ef2f68..5273117 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -428,6 +428,47 @@ DROP SCHEMA pub_test2 CASCADE;
DROP SCHEMA pub_testpart1 CASCADE;
DROP SCHEMA pub_testpart2 CASCADE;
+-- pg_publication_tables
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
+
+
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
--
2.7.2.windows.1
v40-0001-Added-schema-level-support-for-publication.patchapplication/octet-stream; name=v40-0001-Added-schema-level-support-for-publication.patchDownload
From 6ccd0586ccf33f4771d50b0a55691c91fccd93ff Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 7 Oct 2021 19:26:38 +0530
Subject: [PATCH v40 1/5] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
Author: Vignesh C, Amit Kapila, Hou Zhijie
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 303 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 514 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 21 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1350 insertions(+), 188 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..04be11466d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -178,14 +203,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +235,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +287,88 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid, PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +535,176 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ *
+ * Schema will be having both ordinary('r') relkind tables and partitioned('p')
+ * relkind tables, so two rounds of scan are required.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm) &&
+ !(relForm->relispartition && pub_partopt == PUBLICATION_PART_ROOT))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ {
+ List *partitionrels = NIL;
+
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid puboid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(puboid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +832,26 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..55079a604d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,96 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +321,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +573,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +709,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +809,86 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +902,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +926,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +959,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +980,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +999,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1007,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1019,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1059,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1081,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index ddc019cb39..73cd9f04a5 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c2ebe1bf6..e973cd3dd4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15960,6 +15961,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,12 +4810,15 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -4827,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2296,21 +2296,13 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
static bool
_equalCreatePublicationStmt(const CreatePublicationStmt *a,
const CreatePublicationStmt *b)
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..80e8bd0aba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17115,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17302,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..a4c894ec9d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
-extern List *GetPubPartitionOptionRelations(List *result,
- PublicationPartOpt pub_partopt,
- Oid relid);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern List *GetPubPartitionOptionRelations(List* result,
+ PublicationPartOpt pub_partopt,
+ Oid relid);
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v40-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchapplication/octet-stream; name=v40-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 5470d932e083e6c6bc14c954e75d84b64340964e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v40 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 198 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 34 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 352 insertions(+), 54 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..945cbcbd79 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10612,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18848,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0ef..c93478070d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5044,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5053,17 +5078,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6298,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6348,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6413,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6449,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6459,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6487,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..2a0f234622 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,28 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v40-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchapplication/octet-stream; name=v40-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From 177bb1a419e6f6c8cfc6690796a6327bfa24481b Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 8 Sep 2021 16:30:44 +0530
Subject: [PATCH v40 3/5] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 458 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 236 ++++++++-
.../t/025_rep_changes_for_schema.pl | 168 +++++++
8 files changed, 922 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..b4dd0f5444 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..459371b11e 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +388,390 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200adcb..433388ee64 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..3ef2f68eec 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +200,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +226,207 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v40-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchapplication/octet-stream; name=v40-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 840a5c3bea6cd627d604ab057ee98b6dfee168a1 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v40 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 73 ++++++++++++++++++------
doc/src/sgml/ref/create_publication.sgml | 65 ++++++++++++++++++---
3 files changed, 184 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..92de24f6de 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6238,6 +6243,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..f8a79eb7c1 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +115,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +169,20 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..f4d9a73dbf 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,23 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v40-0005-Implemented-pg_publication_objects-view.patchapplication/octet-stream; name=v40-0005-Implemented-pg_publication_objects-view.patchDownload
From f2c80a8313a588cd6cfa7f87ca4834037365e030 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v40 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 92de24f6de..a078302ac8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Fri, Oct 15, 2021 at 6:45 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Thu, Oct 14, 2021 at 9:59 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
If partitions belong to a different schema than the parent partitioned
table, then the current patch implementation allows the partitions to
(optionally) be explicitly added to a publication that includes the
parent partitioned table (and for the most part, it doesn't seem to
make any difference to the publication behavior). Should this be
allowed?e.g.
CREATE SCHEMA sch;
CREATE SCHEMA sch1;
CREATE TABLE sch.sale (sale_date date not null, country_code text,
product_sku text, units integer) PARTITION BY RANGE (sale_date);
CREATE TABLE sch1.sale_201901 PARTITION OF sch.sale FOR VALUES FROM
('2019-01-01') TO ('2019-02-01');
CREATE TABLE sch1.sale_201902 PARTITION OF sch.sale FOR VALUES FROM
('2019-02-01') TO ('2019-03-01');postgres=# CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch, TABLE
sch1.sale_201901, TABLE sch1.sale_201902;
CREATE PUBLICATION
postgres=# \dRp+
Publication pub
Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+------------+---------+---------+---------+-----------+----------
gregn | f | t | t | t | t | f
Tables:
"sch1.sale_201901"
"sch1.sale_201902"
Tables from schemas:
"sch"I don't see any problem with this. Do you have a specific problem in
mind due to this?I'm not sure if it's a problem as such, really just a query from me as
to whether it should be allowed to also (redundantly) add partitions
to the publication, in addition to the partitioned table, since the
current documentation says: "When a partitioned table is added to a
publication, all of its existing and future partitions are implicitly
considered to be part of the publication".
I guess it should be allowed, as I find I can do it in the current
implementation just with TABLE.
I have also checked the "For Table" case and it behaves similar to
what the patch has for schema. So, I think it is better to retain the
current behavior of patch.
--
With Regards,
Amit Kapila.
On Sat, Oct 16, 2021 at 4:57 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Besides, I found we misunderstood the flag PUBLICATION_PART_ROOT it means:
"ROOT: only the table explicitly mentioned in the publication" We cannot use it
as a flag to judge whether do the partition filtering, I think we need to pass
the actual pubviaroot flag.
I agree, PUBLICATION_PART_ROOT can't be used to determine whether to
do partition filtering, the "pubviaroot" flag is needed.
Based on the V40 patchset, attaching the Top-up patch which try to fix the
partition issue in a cleaner way.
A minor thing, in your "top-up patch", the test code added to
publication.sql, you need to remove the last "DROP TABLE
sch2.tbl1_part1;". It causes an error because the table doesn't exist
and it seems to have been erroneously copied from the previous test
case.
Regards,
Greg Nancarrow
Fujitsu Australia
On Saturday, October 16, 2021 1:57 PM houzj.fnst@fujitsu.com wrote:
Based on the V40 patchset, attaching the Top-up patch which try to fix the
partition issue in a cleaner way.
Attach the new version patch set which merge the partition fix into it.
Besides, instead of introducing new function and parameter, just add the
partition filter in pg_get_publication_tables which makes the code cleaner.
Only 0001 and 0003 was changed.
Best regards,
Hou zj
Attachments:
v41-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchapplication/octet-stream; name=v41-0004-Documentation-for-FOR-ALL-TABLES-IN-SCHEMA-publi.patchDownload
From 840a5c3bea6cd627d604ab057ee98b6dfee168a1 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v40 4/5] Documentation for "FOR ALL TABLES IN SCHEMA"
publication.
Documentation for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 73 ++++++++++++++++++------
doc/src/sgml/ref/create_publication.sgml | 65 ++++++++++++++++++---
3 files changed, 184 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..92de24f6de 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6238,6 +6243,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..f8a79eb7c1 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +115,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +169,20 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..f4d9a73dbf 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,23 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v41-0005-Implemented-pg_publication_objects-view.patchapplication/octet-stream; name=v41-0005-Implemented-pg_publication_objects-view.patchDownload
From f2c80a8313a588cd6cfa7f87ca4834037365e030 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v40 5/5] Implemented pg_publication_objects view.
Implemented pg_publication_objects view which displays "FOR TABLE" and
"FOR ALL TABLES IN SCHEMA" publications and the objects they contain.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 20 ++++++++
src/test/regress/expected/rules.out | 16 +++++++
3 files changed, 106 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 92de24f6de..a078302ac8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..079148a364 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,26 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_class C ON C.relnamespace = S.pnnspid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..1af7c53abd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,22 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_class c ON ((c.relnamespace = s.pnnspid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
v41-0001-Added-schema-level-support-for-publication.patchapplication/octet-stream; name=v41-0001-Added-schema-level-support-for-publication.patchDownload
From 6abe0e31d435c64b999138171fd34c03610aaf29 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Mon, 18 Oct 2021 14:07:14 +0800
Subject: [PATCH] Added schema level support for publication.
This patch adds schema-level support for publication.
A new option "FOR ALL TABLES IN SCHEMA" allows one or more schemas to be
specified, whose tables are selected by the publisher for sending the data
to the subscriber.
A new system table "pg_publication_namespace" has been added, to maintain the
schemas that the user wants to publish through the publication. The
schema/publication/publication_namespace dependency was created to handle the
corresponding renaming/removal of schemas to the publication/publication_namespace
when the schema is renamed/dropped. The Decoder identifies if the relation is
part of the publication and replicates it to the subscriber.
CATALOG_VERSION_NO needs to be updated while committing, as this feature
involves a catalog change.
Author: Vignesh C, Amit Kapila, Hou Zhijie
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++++
src/backend/catalog/pg_publication.c | 353 ++++++++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 515 ++++++++++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 ++
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 +++++++++++----
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 ++
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 19 +-
src/include/catalog/pg_publication_namespace.h | 47 +++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1400 insertions(+), 187 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77..4e6efda 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b1..ce0a4ff 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e97..9f8eb1a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939..2bae3fb 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1936,6 +1947,49 @@ get_object_address_publication_rel(List *object,
}
/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
+/*
* Find the ObjectAddress for a default ACL.
*/
static ObjectAddress
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2849,6 +2905,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
}
/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
+/*
* getObjectDescription: build an object description for messages
*
* The result is a palloc'd string. NULL is returned for an undefined
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82..d20eab6 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -77,6 +78,30 @@ check_publication_add_relation(Relation targetrel)
}
/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
+/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
*
@@ -106,6 +131,45 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
}
/*
+ * Filter out the partitions whose parent tables was also specified in
+ * the publication.
+ */
+static List *
+filter_out_partitions(List *relids)
+{
+ List *result = NIL;
+ ListCell *lc;
+ ListCell *lc2;
+
+ foreach(lc, relids)
+ {
+ bool skip = false;
+ List *ancestors = NIL;
+ Oid relid = lfirst_oid(lc);
+
+ if (get_rel_relispartition(relid))
+ ancestors = get_partition_ancestors(relid);
+
+ foreach(lc2, ancestors)
+ {
+ /*
+ * Check if the parent table exists in the published table list.
+ */
+ if (list_member_oid(relids, lfirst_oid(lc2)))
+ {
+ skip = true;
+ break;
+ }
+ }
+
+ if (!skip)
+ result = lappend_oid(result, relid);
+ }
+
+ return result;
+}
+
+/*
* Another variant of this, taking a Relation.
*/
bool
@@ -178,14 +242,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +274,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +326,89 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -429,6 +576,175 @@ GetAllTablesPublicationRelations(bool pubviaroot)
}
/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ *
+ * Schema will be having both ordinary('r') relkind tables and partitioned('p')
+ * relkind tables, so two rounds of scan are required.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+ int keycount = 0;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_RELATION));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the given schema */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+
+ if (is_publishable_class(relid, relForm))
+ result = lappend_oid(result, relid);
+ }
+
+ table_endscan(scan);
+
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(RELKIND_PARTITIONED_TABLE));
+
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /*
+ * It is quite possible that some of the partitions are in a different
+ * schema than the parent table, so we need to get such partitions
+ * separately.
+ */
+ scan = table_beginscan_catalog(classRel, keycount, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (is_publishable_class(relForm->oid, relForm))
+ {
+ List *partitionrels = NIL;
+
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(pubid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
+/*
* Get publication using oid
*
* The Publication struct and its data are palloc'ed here.
@@ -555,12 +871,37 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude partitions
+ * in favor of including the root partitioned tables. Otherwise,
+ * the function could return both the child and parent tables which
+ * could cause the data of child table double-published in
+ * subscriber side.
+ */
+ if (publication->pubviaroot)
+ tables = filter_out_partitions(tables);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e..4004407 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d5..df26432 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f916..d21ed69 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -136,6 +144,96 @@ parse_publication_options(ParseState *pstate,
}
/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
+/*
* Create new publication.
*/
ObjectAddress
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +321,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -441,10 +574,112 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
}
/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
+/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +709,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +809,87 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +903,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +927,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +960,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +981,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +1000,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1008,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -693,6 +1021,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
+/*
* Remove listed tables from the publication.
*/
static void
@@ -704,8 +1060,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -728,6 +1083,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
* Internal workhorse for changing a publication owner
*/
static void
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 308e0ad..53c1862 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c2ebe1..e973cd3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15960,6 +15961,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54..dfa5d8d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,12 +4810,15 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -4827,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20..0532bb2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2297,20 +2297,12 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
}
static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
-static bool
_equalCreatePublicationStmt(const CreatePublicationStmt *a,
const CreatePublicationStmt *b)
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1..80e8bd0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17061,6 +17116,43 @@ TableFuncTypeName(List *columns)
}
/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
+/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
* "AnyName" refers to the any_name production in the grammar.
@@ -17210,6 +17302,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737f..6f6a203 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994..20a8003 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78d..56870b4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35..3eca295 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536..2758d0c 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
+
+extern bool is_publishable_relation(Relation rel);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
-
-extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000..b7e16af
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299b..4ba68c7 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057da..d34b4ac 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877..c75dbec 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -354,6 +354,26 @@ typedef struct RoleSpec
} RoleSpec;
/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
+/*
* FuncCall - a function or aggregate invocation
*
* agg_order (if not NIL) indicates we saw 'foo(... ORDER BY ...)', or if
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348..c8cfbc3 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e94..215eb89 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6af..d04dc66 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec..746566c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.7.2.windows.1
v41-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchapplication/octet-stream; name=v41-0002-Client-side-changes-to-support-FOR-ALL-TABLES-IN.patchDownload
From 5470d932e083e6c6bc14c954e75d84b64340964e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v40 2/5] Client side changes to support "FOR ALL TABLES IN
SCHEMA" publication.
Client side changes to support "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 198 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 34 ++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 352 insertions(+), 54 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..945cbcbd79 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10485,6 +10612,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18717,6 +18848,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0ef..c93478070d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3147,17 +3147,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5021,6 +5044,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5053,17 +5078,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6210,6 +6298,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6225,6 +6348,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6287,15 +6413,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6328,6 +6449,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6337,31 +6459,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6374,6 +6487,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..2a0f234622 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,19 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2697,28 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v41-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchapplication/octet-stream; name=v41-0003-Tests-for-FOR-ALL-TABLES-IN-SCHEMA-publication.patchDownload
From bec2ced111be6bc9e95eb37fc1437921da9551c7 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Mon, 18 Oct 2021 13:29:02 +0800
Subject: [PATCH] Tests for "FOR ALL TABLES IN SCHEMA" publication.
Tests for "FOR ALL TABLES IN SCHEMA" publication.
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 ++
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 515 ++++++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 276 ++++++++++-
.../subscription/t/025_rep_changes_for_schema.pl | 168 +++++++
8 files changed, 1019 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e..28cbe5f 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1..b4dd0f5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a..a9e7f2e 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9b..79ed292 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +388,447 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
+-- pg_publication_tables
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+-----------
+ pub | sch1 | tbl1
+(1 row)
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200a..433388e 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e..2f40156 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d5..837a9ab 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +200,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +226,247 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
+
+-- pg_publication_tables
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
+
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000..6a31017
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.7.2.windows.1
On Monday, October 18, 2021 12:04 PM Greg Nancarrow wrote:
On Sat, Oct 16, 2021 at 4:57 PM houzj.fnst@fujitsu.com wrote:
Based on the V40 patchset, attaching the Top-up patch which try to fix
the partition issue in a cleaner way.A minor thing, in your "top-up patch", the test code added to publication.sql,
you need to remove the last "DROP TABLE sch2.tbl1_part1;". It causes an error
because the table doesn't exist and it seems to have been erroneously copied
from the previous test case.
Thanks for the comment.
I have removed the last "DROP TABLE sch2.tbl1_part1;" in V41 patch set.
Best regards,
Hou zj
Hi,
On Mon, Oct 18, 2021 at 3:14 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Saturday, October 16, 2021 1:57 PM houzj.fnst@fujitsu.com wrote:
Based on the V40 patchset, attaching the Top-up patch which try to fix the
partition issue in a cleaner way.Attach the new version patch set which merge the partition fix into it.
Besides, instead of introducing new function and parameter, just add the
partition filter in pg_get_publication_tables which makes the code cleaner.Only 0001 and 0003 was changed.
I've reviewed 0001 and 0002 patch and here are comments:
0001 patch:
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ *
+ * Schema will be having both ordinary('r') relkind tables and partitioned('p')
+ * relkind tables, so two rounds of scan are required.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[3];
+ TableScanDesc scan;
I think it's enough to have key[2], not key[3].
BTW, this function does the table scan on pg_class twice in order to
get OIDs of both normal tables and partitioned tables. But can't we do
that by the single table scan? I think we can set a scan key for
relnamespace, and check relkind inside a scan loop.
---
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate,
&relations,
+
&schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels,
schemaidlist,
+
PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (!superuser())
+ ereport(ERROR,
+
errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be
superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
Perhaps we can do a superuser check before handling "relations"? If
the user doesn't have the permission, we don't need to do anything for
relations.
0002 patch:
postgres(1:13619)=# create publication pub for all TABLES in schema
CURRENT_SCHEMA pg_catalog public s2
information_schema pg_toast s1
Since pg_catalog and pg_toast cannot be added to the schema
publication can we exclude them from the completion list?
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Mon, Oct 18, 2021 at 2:05 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
Hi,
On Mon, Oct 18, 2021 at 3:14 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:On Saturday, October 16, 2021 1:57 PM houzj.fnst@fujitsu.com wrote:
Based on the V40 patchset, attaching the Top-up patch which try to fix the
partition issue in a cleaner way.Attach the new version patch set which merge the partition fix into it.
Besides, instead of introducing new function and parameter, just add the
partition filter in pg_get_publication_tables which makes the code cleaner.Only 0001 and 0003 was changed.
I've reviewed 0001 and 0002 patch and here are comments:
0001 patch:
+/* + * Get the list of publishable relation oids for a specified schema. + * + * Schema will be having both ordinary('r') relkind tables and partitioned('p') + * relkind tables, so two rounds of scan are required. + */ +List * +GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt) +{ + Relation classRel; + ScanKeyData key[3]; + TableScanDesc scan;I think it's enough to have key[2], not key[3].
Modified
BTW, this function does the table scan on pg_class twice in order to
get OIDs of both normal tables and partitioned tables. But can't we do
that by the single table scan? I think we can set a scan key for
relnamespace, and check relkind inside a scan loop.
Modified
--- + ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations, + &schemaidlist); + + if (list_length(relations) > 0) + { + List *rels; + + rels = OpenTableList(relations); + CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist, + PUBLICATIONOBJ_TABLE); + PublicationAddTables(puboid, rels, true, NULL); + CloseTableList(rels); + } + + if (list_length(schemaidlist) > 0) + { + /* FOR ALL TABLES IN SCHEMA requires superuser */ + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication")); +Perhaps we can do a superuser check before handling "relations"? If
the user doesn't have the permission, we don't need to do anything for
relations.
Modified
0002 patch:
postgres(1:13619)=# create publication pub for all TABLES in schema
CURRENT_SCHEMA pg_catalog public s2
information_schema pg_toast s1Since pg_catalog and pg_toast cannot be added to the schema
publication can we exclude them from the completion list?
Modified
Thanks for the comments, the attached v42 patch has the fixes for the same.
Regards,
Vignesh
Attachments:
v42-0001-Add-support-for-publishing-the-tables-of-schema.patchtext/x-patch; charset=US-ASCII; name=v42-0001-Add-support-for-publishing-the-tables-of-schema.patchDownload
From 05a4e67a5b3f29aebca88e91e9e6233bcde118f4 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Mon, 18 Oct 2021 14:07:14 +0800
Subject: [PATCH v42 1/5] Add support for publishing the tables of schema
To add support to logical replication for publishing the tables of a schema,
the following are added:
* A new system table "pg_publication_namespace", to maintain the schemas that
the user wants to publish.
* Maintenance of schema/publication/publication_namespace dependencies, to
handle the corresponding renaming/removal of schemas to/from the
publication/publication_namespace when a schema is renamed/dropped.
* Modifications to the output plugin (pgoutput), to check if relations are
part of schema publications and to publish the changes.
CATALOG_VERSION_NO needs to be updated while committing, as this feature involves a catalog change.
Author: Vignesh C, Amit Kapila, Hou Zhijie
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 329 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 515 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 15 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1374 insertions(+), 185 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..dbcdadb953 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -76,6 +77,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -105,6 +130,45 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
relid >= FirstNormalObjectId;
}
+/*
+ * Filter out the partitions whose parent tables was also specified in
+ * the publication.
+ */
+static List *
+filter_out_partitions(List *relids)
+{
+ List *result = NIL;
+ ListCell *lc;
+ ListCell *lc2;
+
+ foreach(lc, relids)
+ {
+ bool skip = false;
+ List *ancestors = NIL;
+ Oid relid = lfirst_oid(lc);
+
+ if (get_rel_relispartition(relid))
+ ancestors = get_partition_ancestors(relid);
+
+ foreach(lc2, ancestors)
+ {
+ /*
+ * Check if the parent table exists in the published table list.
+ */
+ if (list_member_oid(relids, lfirst_oid(lc2)))
+ {
+ skip = true;
+ break;
+ }
+ }
+
+ if (!skip)
+ result = lappend_oid(result, relid);
+ }
+
+ return result;
+}
+
/*
* Another variant of this, taking a Relation.
*/
@@ -178,14 +242,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +274,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +326,89 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +575,151 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[1];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the specified schema */
+ scan = table_beginscan_catalog(classRel, 1, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+ char relkind;
+
+ if (!is_publishable_class(relid, relForm))
+ continue;
+
+ relkind = get_rel_relkind(relid);
+ if (relkind == RELKIND_RELATION)
+ result = lappend_oid(result, relid);
+ else if (relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *partitionrels = NIL;
+
+ /*
+ * It is quite possible that some of the partitions are in a
+ * different schema than the parent table, so we need to get such
+ * partitions separately.
+ */
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(pubid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +847,37 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude partitions
+ * in favor of including the root partitioned tables. Otherwise,
+ * the function could return both the child and parent tables which
+ * could cause the data of child table double-published in
+ * subscriber side.
+ */
+ if (publication->pubviaroot)
+ tables = filter_out_partitions(tables);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..1d85e19b34 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,22 +36,28 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,96 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -165,6 +265,12 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create FOR ALL TABLES publication")));
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (list_length(schemaidlist) > 0 && !superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
rel = table_open(PublicationRelationId, RowExclusiveLock);
/* Check if name is used */
@@ -221,21 +327,38 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +573,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +709,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +809,87 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +903,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +927,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +960,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +981,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +1000,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1008,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1020,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1060,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1082,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 308e0adb55..53c18628a7 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c2ebe1bf6..e973cd3dd4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15960,6 +15961,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,12 +4810,15 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -4827,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2296,21 +2296,13 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
static bool
_equalCreatePublicationStmt(const CreatePublicationStmt *a,
const CreatePublicationStmt *b)
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..80e8bd0aba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17115,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17302,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..cea0d887e1 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v42-0002-Add-client-side-support-to-logical-replication-f.patchtext/x-patch; charset=US-ASCII; name=v42-0002-Add-client-side-support-to-logical-replication-f.patchDownload
From 531df11f200d611abeea2dc1391e20861809ac5a Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v42 2/5] Add client-side support to logical replication for
publishing the tables of a schema.
To add client-side support to logical replication for publishing the tables of
a schema, the following are added:
* pg_dump updates to support identifying and dumping schema publications
* Tab completion syntax updates for create/alter schema publications
* psql \d support for tables, to display the schema publication(s)
* psql \d support for publications, to display the schema(s)
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 198 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 40 +++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 358 insertions(+), 54 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6ec524f8e6..3c4d3d1e14 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10486,6 +10613,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18718,6 +18849,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c..c7f97476d1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3146,17 +3146,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5020,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5052,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6209,6 +6297,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6286,15 +6412,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6327,6 +6448,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6336,31 +6458,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6373,6 +6486,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..7d53379a2c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,22 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2700,31 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v42-0003-Add-tests-for-the-schema-publication-feature-of-.patchtext/x-patch; charset=US-ASCII; name=v42-0003-Add-tests-for-the-schema-publication-feature-of-.patchDownload
From e78e524c624acb4255e0209d1e96a79f0191f3a9 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Mon, 18 Oct 2021 13:29:02 +0800
Subject: [PATCH v42 3/5] Add tests for the schema publication feature of
logical replication
Schema publication tests are added to verify the following:
* Invalidation
* Add/Drop/Set ALL TABLES IN SCHEMA
* Schema publication handling in pg_dump
* Replication of schema publications
* psql \d display of publications and tables
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 515 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 276 +++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
8 files changed, 1019 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..b4dd0f5444 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4489,3 +4489,17 @@ select indexrelid::regclass, indisclustered from pg_index
(2 rows)
drop table alttype_cluster;
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..79ed292c18 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,21 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +388,447 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
+-- pg_publication_tables
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+-----------
+ pub | sch1 | tbl1
+(1 row)
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dc0200adcb..433388ee64 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2940,3 +2940,15 @@ select indexrelid::regclass, indisclustered from pg_index
where indrelid = 'alttype_cluster'::regclass
order by indexrelid::regclass::text;
drop table alttype_cluster;
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..837a9ab910 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,11 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +200,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +226,247 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
+
+-- pg_publication_tables
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
+
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v42-0004-Add-documentation-for-the-schema-publication-fea.patchtext/x-patch; charset=US-ASCII; name=v42-0004-Add-documentation-for-the-schema-publication-fea.patchDownload
From 2ace46f39dfe05b4f889bc1a62639ce8798272ee Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v42 4/5] Add documentation for the schema publication feature
of logical replication
The following schema publication documentation is added:
* Create/alter publication syntax, with examples
* pg_publication_namespace system table
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 ++++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 73 ++++++++++++++++++------
doc/src/sgml/ref/create_publication.sgml | 65 ++++++++++++++++++---
3 files changed, 184 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..92de24f6de 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6238,6 +6243,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..f8a79eb7c1 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,16 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication. Note that adding tables/schemas to a
+ publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +70,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +115,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +169,20 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june;
+</programlisting>
+ </para>
+
+ <para>
+ Add some tables and schemas to the publication:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..f4d9a73dbf 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,23 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas "marketing" and "sales":
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v42-0005-Add-new-pg_publication_objects-view-to-display-T.patchtext/x-patch; charset=US-ASCII; name=v42-0005-Add-new-pg_publication_objects-view-to-display-T.patchDownload
From dccd75cff15fc9be99cb0e13643601c442b14e1e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v42 5/5] Add new "pg_publication_objects" view to display
"TABLE"/"SCHEMA" publication objects
A new "pg_publication_objects" view is added, to display table/schema object
information associated with publications.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 19 ++++++++
src/test/regress/expected/rules.out | 15 ++++++
3 files changed, 104 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 92de24f6de..a078302ac8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..f70348e34f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,25 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..8796f71de2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,21 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM ((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Monday, October 18, 2021 8:23 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v42 patch has the fixes for the same.
Thanks for your new patch.
I tried your patch and found that the permission check for superuser didn't work.
For example:
postgres=# create role r1;
CREATE ROLE
postgres=# grant all privileges on database postgres to r1;
GRANT
postgres=# set role r1;
SET
postgres=> create schema s1;
CREATE SCHEMA
postgres=> create publication pub for all tables in schema s1;
CREATE PUBLICATION
Role r1 is not superuser, but this role could create publication for all tables in schema
successfully, I think it is related the following change. List schemaidlist was
not assigned yet. I think we should check it later.
@@ -165,6 +265,12 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create FOR ALL TABLES publication")));
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (list_length(schemaidlist) > 0 && !superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
rel = table_open(PublicationRelationId, RowExclusiveLock);
/* Check if name is used */
Regards
Tang
On Tue, Oct 19, 2021 at 9:15 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Monday, October 18, 2021 8:23 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v42 patch has the fixes for the same.
Thanks for your new patch.
I tried your patch and found that the permission check for superuser didn't work.
For example:
postgres=# create role r1;
CREATE ROLE
postgres=# grant all privileges on database postgres to r1;
GRANT
postgres=# set role r1;
SET
postgres=> create schema s1;
CREATE SCHEMA
postgres=> create publication pub for all tables in schema s1;
CREATE PUBLICATIONRole r1 is not superuser, but this role could create publication for all tables in schema
successfully, I think it is related the following change. List schemaidlist was
not assigned yet. I think we should check it later.
It seems this got broken in yesterday's patch version. Do you think it
is a good idea to add a test for this case?
--
With Regards,
Amit Kapila.
On Tuesday, October 19, 2021 12:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Oct 19, 2021 at 9:15 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:On Monday, October 18, 2021 8:23 PM vignesh C <vignesh21@gmail.com>
wrote:
Thanks for the comments, the attached v42 patch has the fixes for the same.
Thanks for your new patch.
I tried your patch and found that the permission check for superuser didn't work.
For example:
postgres=# create role r1;
CREATE ROLE
postgres=# grant all privileges on database postgres to r1;
GRANT
postgres=# set role r1;
SET
postgres=> create schema s1;
CREATE SCHEMA
postgres=> create publication pub for all tables in schema s1;
CREATE PUBLICATIONRole r1 is not superuser, but this role could create publication for all tables in
schema
successfully, I think it is related the following change. List schemaidlist was
not assigned yet. I think we should check it later.It seems this got broken in yesterday's patch version. Do you think it
is a good idea to add a test for this case?
Agreed. Thanks for your suggestion.
I tried to add this test to publication.sql, a patch diff file for this test is attached.
Regards
Tang
Attachments:
Topup-permissions-test_diffapplication/octet-stream; name=Topup-permissions-test_diffDownload
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 79ed292c18..971e8fa66c 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -342,6 +342,8 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to create FOR ALL TABLES IN SCHEMA publication
CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 837a9ab910..a0b0406713 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -188,6 +188,7 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
On Mon, Oct 18, 2021 at 5:53 PM vignesh C <vignesh21@gmail.com> wrote:
Few comments on latest set of patches:
===============================
1.
+/*
+ * Filter out the partitions whose parent tables was also specified in
+ * the publication.
+ */
+static List *
+filter_out_partitions(List *relids)
Can we name this function as filter_partitions()?
2.
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude partitions
+ * in favor of including the root partitioned tables. Otherwise,
+ * the function could return both the child and parent tables which
+ * could cause the data of child table double-published in
+ * subscriber side.
+ */
Let's slightly change the last part of the line in the above comment
as: "... which could cause data of the child table to be
double-published on the subscriber side."
3.
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
..
..
@@ -38,7 +40,6 @@
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
-#include "utils/inval.h"
#include "utils/lsyscache.h"
Does this change belong to this patch? If not, maybe you can submit a
separate patch for this. A similar change is present in
publicationcmds.c as well, not sure if that is required as well.
4.
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
...
...
+#include "nodes/makefuncs.h"
Do we need to include this file? I am able to compile without
including this file.
v42-0003-Add-tests-for-the-schema-publication-feature-of-
5.
+-- pg_publication_tables
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM
(1) to (10);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH
(PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH
(PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
Can we expand the above comment on the lines of: "Test the list of
partitions published"?
v42-0004-Add-documentation-for-the-schema-publication-fea
6.
+ <row>
+ <entry><link
linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link
linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6238,6 +6243,67 @@ SCRAM-SHA-256$<replaceable><iteration
count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
At one place, the new catalog is placed after pg_publication_rel and
at another place, it is before it. Shouldn't it be before in both
places as we have a place as per naming order?
7.
The
+ <literal>ADD</literal> clause will add one or more tables/schemas to the
+ publication. The <literal>DROP</literal> clauses will remove one or more
+ tables/schemas from the publication.
Isn't it better to write the above as one line: "The
<literal>ADD</literal> and <literal>DROP</literal> clauses will add
and remove one or more tables/schemas from the publication."?
8.
+ <para>
+ Add some schemas to the publication:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA
marketing_june, sales_june;
+</programlisting>
+ </para>
Can we change schema names to just marketing and sales? Also, let's
change the description as:"Add schemas
<structname>marketing</structname> and <structname>sales</structname>
to the publication <structname>sales_publication</structname>?
9.
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication
object</replaceable> [, ... ] ]
[ WITH ( <replaceable
class="parameter">publication_parameter</replaceable> [= <replaceable
class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication
object</replaceable> is one of:</phrase>
Similar to Alter Publication, here also we should use
publication_object instead of publication object.
10.
+ <para>
+ Create a publication that publishes all changes for tables "users" and
+ "departments" and that publishes all changes for all the tables present in
+ the schema "production":
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users,
departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables
present in
+ the schemas "marketing" and "sales":
It is better to use <structname> before and </structname> after schema
names in above descriptions.
--
With Regards,
Amit Kapila.
On Tue, Oct 19, 2021 at 9:15 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Monday, October 18, 2021 8:23 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v42 patch has the fixes for the same.
Thanks for your new patch.
I tried your patch and found that the permission check for superuser didn't work.
For example:
postgres=# create role r1;
CREATE ROLE
postgres=# grant all privileges on database postgres to r1;
GRANT
postgres=# set role r1;
SET
postgres=> create schema s1;
CREATE SCHEMA
postgres=> create publication pub for all tables in schema s1;
CREATE PUBLICATIONRole r1 is not superuser, but this role could create publication for all tables in schema
successfully, I think it is related the following change. List schemaidlist was
not assigned yet. I think we should check it later.@@ -165,6 +265,12 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create FOR ALL TABLES publication")));+ /* FOR ALL TABLES IN SCHEMA requires superuser */ + if (list_length(schemaidlist) > 0 && !superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication")); + rel = table_open(PublicationRelationId, RowExclusiveLock);/* Check if name is used */
This issue got induced in the v42 version, attached v43 patch has the
fixes for the same.
Regards,
Vignesh
Attachments:
v43-0001-Add-support-for-publishing-the-tables-of-schema.patchtext/x-patch; charset=US-ASCII; name=v43-0001-Add-support-for-publishing-the-tables-of-schema.patchDownload
From 4455795fa67eb75c4ed2a3e10677781528258836 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Mon, 18 Oct 2021 14:07:14 +0800
Subject: [PATCH v43 1/4] Add support for publishing the tables of schema
To add support to logical replication for publishing the tables of a schema,
the following are added:
* A new system table "pg_publication_namespace", to maintain the schemas that
the user wants to publish.
* Maintenance of schema/publication/publication_namespace dependencies, to
handle the corresponding renaming/removal of schemas to/from the
publication/publication_namespace when a schema is renamed/dropped.
* Modifications to the output plugin (pgoutput), to check if relations are
part of schema publications and to publish the changes.
CATALOG_VERSION_NO needs to be updated while committing, as this feature involves a catalog change.
Author: Vignesh C, Amit Kapila, Hou Zhijie
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 328 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 513 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 ++++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 15 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1373 insertions(+), 183 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..8e268295f9 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -76,6 +78,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -105,6 +131,45 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
relid >= FirstNormalObjectId;
}
+/*
+ * Filter out the partitions whose parent tables were also specified in
+ * the publication.
+ */
+static List *
+filter_partitions(List *relids)
+{
+ List *result = NIL;
+ ListCell *lc;
+ ListCell *lc2;
+
+ foreach(lc, relids)
+ {
+ bool skip = false;
+ List *ancestors = NIL;
+ Oid relid = lfirst_oid(lc);
+
+ if (get_rel_relispartition(relid))
+ ancestors = get_partition_ancestors(relid);
+
+ foreach(lc2, ancestors)
+ {
+ /*
+ * Check if the parent table exists in the published table list.
+ */
+ if (list_member_oid(relids, lfirst_oid(lc2)))
+ {
+ skip = true;
+ break;
+ }
+ }
+
+ if (!skip)
+ result = lappend_oid(result, relid);
+ }
+
+ return result;
+}
+
/*
* Another variant of this, taking a Relation.
*/
@@ -178,14 +243,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +275,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +327,89 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +576,151 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[1];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the specified schema */
+ scan = table_beginscan_catalog(classRel, 1, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+ char relkind;
+
+ if (!is_publishable_class(relid, relForm))
+ continue;
+
+ relkind = get_rel_relkind(relid);
+ if (relkind == RELKIND_RELATION)
+ result = lappend_oid(result, relid);
+ else if (relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *partitionrels = NIL;
+
+ /*
+ * It is quite possible that some of the partitions are in a
+ * different schema than the parent table, so we need to get such
+ * partitions separately.
+ */
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(pubid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +848,37 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude partitions
+ * in favor of including the root partitioned tables. Otherwise,
+ * the function could return both the child and parent tables which
+ * could cause data of the child table to be double-published on the
+ * subscriber side.
+ */
+ if (publication->pubviaroot)
+ tables = filter_partitions(tables);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..410f98e09d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,6 +36,7 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -45,11 +48,16 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,96 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +321,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (list_length(schemaidlist) > 0 && !superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +573,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +709,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +809,87 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +903,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +927,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +960,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +981,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +1000,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1008,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1020,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1060,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1082,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 308e0adb55..53c18628a7 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 487852a14e..3b10ce885d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15960,6 +15961,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,12 +4810,15 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -4827,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2296,21 +2296,13 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
static bool
_equalCreatePublicationStmt(const CreatePublicationStmt *a,
const CreatePublicationStmt *b)
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..80e8bd0aba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17115,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17302,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..cea0d887e1 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v43-0002-Add-client-side-support-to-logical-replication-f.patchtext/x-patch; charset=US-ASCII; name=v43-0002-Add-client-side-support-to-logical-replication-f.patchDownload
From f0cb301dbda76b4e3ef681ca6804e0d3e077f09e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v43 2/4] Add client-side support to logical replication for
publishing the tables of a schema.
To add client-side support to logical replication for publishing the tables of
a schema, the following are added:
* pg_dump updates to support identifying and dumping schema publications
* Tab completion syntax updates for create/alter schema publications
* psql \d support for tables, to display the schema publication(s)
* psql \d support for publications, to display the schema(s)
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 198 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 40 +++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 358 insertions(+), 54 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6ec524f8e6..3c4d3d1e14 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10486,6 +10613,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18718,6 +18849,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c..c7f97476d1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3146,17 +3146,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5020,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5052,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6209,6 +6297,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6286,15 +6412,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6327,6 +6448,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6336,31 +6458,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6373,6 +6486,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..7d53379a2c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,22 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2700,31 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v43-0003-Add-tests-for-the-schema-publication-feature-of-.patchtext/x-patch; charset=US-ASCII; name=v43-0003-Add-tests-for-the-schema-publication-feature-of-.patchDownload
From 3d8c03a287302bf78f5fc2cf6327e5e6023d0c6f Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Tue, 19 Oct 2021 17:20:51 +0530
Subject: [PATCH v43 3/4] Add tests for the schema publication feature of
logical replication
Schema publication tests are added to verify the following:
* Invalidation
* Add/Drop/Set ALL TABLES IN SCHEMA
* Schema publication handling in pg_dump
* Replication of schema publications
* psql \d display of publications and tables
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 521 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 280 +++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
8 files changed, 1029 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index fa54bef89a..a27d8522af 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4509,3 +4509,17 @@ DETAIL: Failing row contains (2, 1).
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..c17d19bd63 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,23 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to create FOR ALL TABLES IN SCHEMA publication
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +390,451 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
+-- Test the list of partitions published
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+-----------
+ pub | sch1 | tbl1
+(1 row)
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 0c61604456..a3f0fac0ee 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2959,3 +2959,15 @@ insert into attach_parted_part1 values (2, 1);
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..419e6fbc70 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,12 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +201,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +227,250 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
+
+-- Test the list of partitions published
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v43-0004-Add-documentation-for-the-schema-publication-fea.patchtext/x-patch; charset=US-ASCII; name=v43-0004-Add-documentation-for-the-schema-publication-fea.patchDownload
From 0ba50486bab6b52e05a831a8acdb8d13ffed4608 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v43 4/4] Add documentation for the schema publication feature
of logical replication
The following schema publication documentation is added:
* Create/alter publication syntax, with examples
* pg_publication_namespace system table
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 +++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 77 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 67 ++++++++++++++++++---
3 files changed, 190 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..7bf132f8cd 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6176,6 +6181,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-publication-rel">
<title><structname>pg_publication_rel</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..2b13d18efb 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> and <literal>DROP</literal> clause will add and
+ remove one or more tables/schemas from the publication. Note that adding
+ tables/schemas to a publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +69,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +114,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +168,25 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add schemas <structname>marketing</structname> and
+ <structname>sales</structname> to the publication
+ <structname>sales_publication</structname>:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing, sales;
+</programlisting>
+ </para>
+
+ <para>
+ Add tables <structname>users</structname>,
+ <structname>departments</structname> and schema
+ <structname>production</structname> to the publication
+ <structname>production_publication</structname>:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..2c6bdfbac0 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication_object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,25 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables
+ <structname>users</structname>, <structname>departments</structname> and
+ that publishes all changes for all the tables present in the schema
+ <structname>production</structname>:
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas <structname>marketing</structname> and
+ <structname>sales</structname>:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v43-0005-Add-new-pg_publication_objects-view-to-display-T.patchtext/x-patch; charset=US-ASCII; name=v43-0005-Add-new-pg_publication_objects-view-to-display-T.patchDownload
From 04b4f50256c411989761fa30163d79f446a4647f Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v43 5/5] Add new "pg_publication_objects" view to display
"TABLE"/"SCHEMA" publication objects
A new "pg_publication_objects" view is added, to display table/schema object
information associated with publications.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 19 ++++++++
src/test/regress/expected/rules.out | 15 ++++++
3 files changed, 104 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 92de24f6de..a078302ac8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..f70348e34f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,25 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..8796f71de2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,21 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM ((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Tue, Oct 19, 2021 at 11:23 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:
On Tuesday, October 19, 2021 12:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Oct 19, 2021 at 9:15 AM tanghy.fnst@fujitsu.com
<tanghy.fnst@fujitsu.com> wrote:On Monday, October 18, 2021 8:23 PM vignesh C <vignesh21@gmail.com>
wrote:
Thanks for the comments, the attached v42 patch has the fixes for the same.
Thanks for your new patch.
I tried your patch and found that the permission check for superuser didn't work.
For example:
postgres=# create role r1;
CREATE ROLE
postgres=# grant all privileges on database postgres to r1;
GRANT
postgres=# set role r1;
SET
postgres=> create schema s1;
CREATE SCHEMA
postgres=> create publication pub for all tables in schema s1;
CREATE PUBLICATIONRole r1 is not superuser, but this role could create publication for all tables in
schema
successfully, I think it is related the following change. List schemaidlist was
not assigned yet. I think we should check it later.It seems this got broken in yesterday's patch version. Do you think it
is a good idea to add a test for this case?Agreed. Thanks for your suggestion.
I tried to add this test to publication.sql, a patch diff file for this test is attached.
Thanks for the test case, I have merged it to the v43 version patch
attached at [1]/messages/by-id/CALDaNm2pJ49wAv=gEZrAP5=_apAzv_rgK3zjX-wfwCY+WWfT9w@mail.gmail.com.
[1]: /messages/by-id/CALDaNm2pJ49wAv=gEZrAP5=_apAzv_rgK3zjX-wfwCY+WWfT9w@mail.gmail.com
Regards,
Vignesh
On Tue, Oct 19, 2021 at 4:59 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Oct 18, 2021 at 5:53 PM vignesh C <vignesh21@gmail.com> wrote:
Few comments on latest set of patches: =============================== 1. +/* + * Filter out the partitions whose parent tables was also specified in + * the publication. + */ +static List * +filter_out_partitions(List *relids)Can we name this function as filter_partitions()?
Modified
2. + /* + * If the publication publishes partition changes via their + * respective root partitioned tables, we must exclude partitions + * in favor of including the root partitioned tables. Otherwise, + * the function could return both the child and parent tables which + * could cause the data of child table double-published in + * subscriber side. + */Let's slightly change the last part of the line in the above comment
as: "... which could cause data of the child table to be
double-published on the subscriber side."
Modified
3. --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c .. .. @@ -38,7 +40,6 @@ #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/fmgroids.h" -#include "utils/inval.h" #include "utils/lsyscache.h"Does this change belong to this patch? If not, maybe you can submit a
separate patch for this. A similar change is present in
publicationcmds.c as well, not sure if that is required as well.
I have removed these changes from this patch, I will post a patch for
this separately later.
4. --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c ... ... +#include "nodes/makefuncs.h"Do we need to include this file? I am able to compile without
including this file.
Modified
v42-0003-Add-tests-for-the-schema-publication-feature-of- 5. +-- pg_publication_tables +SET client_min_messages = 'ERROR'; +CREATE SCHEMA sch1; +CREATE SCHEMA sch2; +CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a); +CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10); +CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1); +SELECT * FROM pg_publication_tables; + +DROP PUBLICATION pub; +CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1); +SELECT * FROM pg_publication_tables;Can we expand the above comment on the lines of: "Test the list of
partitions published"?
Modified
v42-0004-Add-documentation-for-the-schema-publication-fea 6. + <row> + <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry> + <entry>schema to publication mapping</entry> + </row> + <row> <entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry> <entry>relation to publication mapping</entry> @@ -6238,6 +6243,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </table> </sect1>+ <sect1 id="catalog-pg-publication-namespace"> + <title><structname>pg_publication_namespace</structname></title>At one place, the new catalog is placed after pg_publication_rel and
at another place, it is before it. Shouldn't it be before in both
places as we have a place as per naming order?
Modified
7. The + <literal>ADD</literal> clause will add one or more tables/schemas to the + publication. The <literal>DROP</literal> clauses will remove one or more + tables/schemas from the publication.Isn't it better to write the above as one line: "The
<literal>ADD</literal> and <literal>DROP</literal> clauses will add
and remove one or more tables/schemas from the publication."?
Modified
8. + <para> + Add some schemas to the publication: +<programlisting> +ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing_june, sales_june; +</programlisting> + </para>Can we change schema names to just marketing and sales? Also, let's
change the description as:"Add schemas
<structname>marketing</structname> and <structname>sales</structname>
to the publication <structname>sales_publication</structname>?
Modified
9. + [ FOR ALL TABLES + | FOR <replaceable class="parameter">publication object</replaceable> [, ... ] ] [ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ] + +<phrase>where <replaceable class="parameter">publication object</replaceable> is one of:</phrase>Similar to Alter Publication, here also we should use
publication_object instead of publication object.
Modified
10. + <para> + Create a publication that publishes all changes for tables "users" and + "departments" and that publishes all changes for all the tables present in + the schema "production": +<programlisting> +CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production; +</programlisting> + </para> + + <para> + Create a publication that publishes all changes for all the tables present in + the schemas "marketing" and "sales":It is better to use <structname> before and </structname> after schema
names in above descriptions.
Modified
Attached v43 patch has the fixes for the same.
Regards,
Vignesh
Attachments:
v43-0001-Add-support-for-publishing-the-tables-of-schema.patchapplication/x-patch; name=v43-0001-Add-support-for-publishing-the-tables-of-schema.patchDownload
From 4455795fa67eb75c4ed2a3e10677781528258836 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Mon, 18 Oct 2021 14:07:14 +0800
Subject: [PATCH v43 1/4] Add support for publishing the tables of schema
To add support to logical replication for publishing the tables of a schema,
the following are added:
* A new system table "pg_publication_namespace", to maintain the schemas that
the user wants to publish.
* Maintenance of schema/publication/publication_namespace dependencies, to
handle the corresponding renaming/removal of schemas to/from the
publication/publication_namespace when a schema is renamed/dropped.
* Modifications to the output plugin (pgoutput), to check if relations are
part of schema publications and to publish the changes.
CATALOG_VERSION_NO needs to be updated while committing, as this feature involves a catalog change.
Author: Vignesh C, Amit Kapila, Hou Zhijie
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 328 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 513 +++++++++++++++---
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 21 +-
src/backend/nodes/equalfuncs.c | 30 +-
src/backend/parser/gram.y | 302 ++++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 15 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 2 +-
src/include/nodes/parsenodes.h | 39 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 5 +-
26 files changed, 1373 insertions(+), 183 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..8e268295f9 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -76,6 +78,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -105,6 +131,45 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
relid >= FirstNormalObjectId;
}
+/*
+ * Filter out the partitions whose parent tables were also specified in
+ * the publication.
+ */
+static List *
+filter_partitions(List *relids)
+{
+ List *result = NIL;
+ ListCell *lc;
+ ListCell *lc2;
+
+ foreach(lc, relids)
+ {
+ bool skip = false;
+ List *ancestors = NIL;
+ Oid relid = lfirst_oid(lc);
+
+ if (get_rel_relispartition(relid))
+ ancestors = get_partition_ancestors(relid);
+
+ foreach(lc2, ancestors)
+ {
+ /*
+ * Check if the parent table exists in the published table list.
+ */
+ if (list_member_oid(relids, lfirst_oid(lc2)))
+ {
+ skip = true;
+ break;
+ }
+ }
+
+ if (!skip)
+ result = lappend_oid(result, relid);
+ }
+
+ return result;
+}
+
/*
* Another variant of this, taking a Relation.
*/
@@ -178,14 +243,14 @@ GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
* Insert new publication / relation mapping.
*/
ObjectAddress
-publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists)
{
Relation rel;
HeapTuple tup;
Datum values[Natts_pg_publication_rel];
bool nulls[Natts_pg_publication_rel];
- Oid relid = RelationGetRelid(targetrel->relation);
+ Oid relid = RelationGetRelid(targetrel);
Oid prrelid;
Publication *pub = GetPublication(pubid);
ObjectAddress myself,
@@ -210,10 +275,10 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("relation \"%s\" is already member of publication \"%s\"",
- RelationGetRelationName(targetrel->relation), pub->name)));
+ RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(targetrel->relation);
+ check_publication_add_relation(targetrel);
/* Form a tuple. */
memset(values, 0, sizeof(values));
@@ -262,6 +327,89 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +576,151 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[1];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the specified schema */
+ scan = table_beginscan_catalog(classRel, 1, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+ char relkind;
+
+ if (!is_publishable_class(relid, relForm))
+ continue;
+
+ relkind = get_rel_relkind(relid);
+ if (relkind == RELKIND_RELATION)
+ result = lappend_oid(result, relid);
+ else if (relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *partitionrels = NIL;
+
+ /*
+ * It is quite possible that some of the partitions are in a
+ * different schema than the parent table, so we need to get such
+ * partitions separately.
+ */
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(pubid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +848,37 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ tables = list_concat_unique_oid(relids, schemarelids);
+
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude partitions
+ * in favor of including the root partitioned tables. Otherwise,
+ * the function could return both the child and parent tables which
+ * could cause data of the child table to be double-published on the
+ * subscriber side.
+ */
+ if (publication->pubviaroot)
+ tables = filter_partitions(tables);
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..410f98e09d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,6 +36,7 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -45,11 +48,16 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,96 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->rangevar);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ Relation rel = (Relation) lfirst(lc);
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +250,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +321,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (list_length(schemaidlist) > 0 && !superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +441,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +490,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +528,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -400,10 +540,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
foreach(newlc, rels)
{
- PublicationRelInfo *newpubrel;
+ Relation newrel = (Relation) lfirst(newlc);
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- if (RelationGetRelid(newpubrel->relation) == oldrelid)
+ if (RelationGetRelid(newrel) == oldrelid)
{
found = true;
break;
@@ -412,16 +551,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
/* Not yet in the list, open it and add to the list */
if (!found)
{
- Relation oldrel;
- PublicationRelInfo *pubrel;
-
- /* Wrap relation into PublicationRelInfo */
- oldrel = table_open(oldrelid, ShareUpdateExclusiveLock);
-
- pubrel = palloc(sizeof(PublicationRelInfo));
- pubrel->relation = oldrel;
+ Relation oldrel = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
- delrels = lappend(delrels, pubrel);
+ delrels = lappend(delrels, oldrel);
}
}
@@ -440,11 +573,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +709,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +809,87 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ rels = lappend(rels, rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -568,16 +903,15 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
- bool recurse = t->relation->inh;
+ RangeVar *rv = lfirst_node(RangeVar, lc);
+ bool recurse = rv->inh;
Relation rel;
Oid myrelid;
- PublicationRelInfo *pub_rel;
/* Allow query cancel in case this takes a long time */
CHECK_FOR_INTERRUPTS();
- rel = table_openrv(t->relation, ShareUpdateExclusiveLock);
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
myrelid = RelationGetRelid(rel);
/*
@@ -593,9 +927,7 @@ OpenTableList(List *tables)
continue;
}
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
/*
@@ -628,9 +960,7 @@ OpenTableList(List *tables)
/* find_all_inheritors already got lock */
rel = table_open(childrelid, NoLock);
- pub_rel = palloc(sizeof(PublicationRelInfo));
- pub_rel->relation = rel;
- rels = lappend(rels, pub_rel);
+ rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
}
}
@@ -651,10 +981,9 @@ CloseTableList(List *rels)
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel;
+ Relation rel = (Relation) lfirst(lc);
- pub_rel = (PublicationRelInfo *) lfirst(lc);
- table_close(pub_rel->relation, NoLock);
+ table_close(rel, NoLock);
}
}
@@ -671,8 +1000,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
foreach(lc, rels)
{
- PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pub_rel->relation;
+ Relation rel = (Relation) lfirst(lc);
ObjectAddress obj;
/* Must be owner of the table or superuser. */
@@ -680,7 +1008,7 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists);
+ obj = publication_add_relation(pubid, rel, if_not_exists);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
@@ -692,6 +1020,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -704,8 +1060,7 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
foreach(lc, rels)
{
- PublicationRelInfo *pubrel = (PublicationRelInfo *) lfirst(lc);
- Relation rel = pubrel->relation;
+ Relation rel = (Relation) lfirst(lc);
Oid relid = RelationGetRelid(rel);
prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
@@ -727,6 +1082,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 308e0adb55..53c18628a7 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 487852a14e..3b10ce885d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12252,6 +12252,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15960,6 +15961,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..dfa5d8d705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,12 +4810,15 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
-static PublicationTable *
-_copyPublicationTable(const PublicationTable *from)
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
{
- PublicationTable *newnode = makeNode(PublicationTable);
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
- COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(rangevar);
+ COPY_LOCATION_FIELD(location);
return newnode;
}
@@ -4827,7 +4830,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4843,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,8 +5890,8 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
- case T_PublicationTable:
- retval = _copyPublicationTable(from);
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
break;
/*
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..0532bb20ee 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2296,21 +2296,13 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
-static bool
-_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
-{
- COMPARE_NODE_FIELD(relation);
-
- return true;
-}
-
static bool
_equalCreatePublicationStmt(const CreatePublicationStmt *a,
const CreatePublicationStmt *b)
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2314,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3038,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(rangevar);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3894,8 +3898,8 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
- case T_PublicationTable:
- retval = _equalPublicationTable(a, b);
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..80e8bd0aba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,128 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->rangevar = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->name = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->rangevar = $1;
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9731,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12499,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15180,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17115,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17302,72 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->rangevar)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ pubobj->rangevar = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->rangevar)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..cea0d887e1 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -83,11 +83,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -111,13 +106,21 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..d34b4ac8e5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,7 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_PublicationTable,
+ T_PublicationObjSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..c75dbece52 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,26 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ RangeVar *rangevar;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1836,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3636,18 +3657,12 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
-{
- NodeTag type;
- RangeVar *relation; /* relation to be published */
-} PublicationTable;
-
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3674,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..746566c01a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,9 +2048,10 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
-PublicationTable
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v43-0002-Add-client-side-support-to-logical-replication-f.patchapplication/x-patch; name=v43-0002-Add-client-side-support-to-logical-replication-f.patchDownload
From f0cb301dbda76b4e3ef681ca6804e0d3e077f09e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sun, 12 Sep 2021 20:32:28 +0530
Subject: [PATCH v43 2/4] Add client-side support to logical replication for
publishing the tables of a schema.
To add client-side support to logical replication for publishing the tables of
a schema, the following are added:
* pg_dump updates to support identifying and dumping schema publications
* Tab completion syntax updates for create/alter schema publications
* psql \d support for tables, to display the schema publication(s)
* psql \d support for publications, to display the schema(s)
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 198 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 40 +++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 358 insertions(+), 54 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6ec524f8e6..3c4d3d1e14 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10486,6 +10613,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18718,6 +18849,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c..c7f97476d1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3146,17 +3146,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5020,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5052,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6209,6 +6297,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6286,15 +6412,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6327,6 +6448,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6336,31 +6458,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6373,6 +6486,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..7d53379a2c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,22 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2700,31 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 746566c01a..cd3736b7a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PullFilter
PullFilterOps
PushFilter
--
2.30.2
v43-0003-Add-tests-for-the-schema-publication-feature-of-.patchapplication/x-patch; name=v43-0003-Add-tests-for-the-schema-publication-feature-of-.patchDownload
From 3d8c03a287302bf78f5fc2cf6327e5e6023d0c6f Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Tue, 19 Oct 2021 17:20:51 +0530
Subject: [PATCH v43 3/4] Add tests for the schema publication feature of
logical replication
Schema publication tests are added to verify the following:
* Invalidation
* Add/Drop/Set ALL TABLES IN SCHEMA
* Schema publication handling in pg_dump
* Replication of schema publications
* psql \d display of publications and tables
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 521 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 280 +++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
8 files changed, 1029 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index fa54bef89a..a27d8522af 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4509,3 +4509,17 @@ DETAIL: Failing row contains (2, 1).
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..c17d19bd63 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,23 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to create FOR ALL TABLES IN SCHEMA publication
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +390,451 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
+-- Test the list of partitions published
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+-----------
+ pub | sch1 | tbl1
+(1 row)
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 0c61604456..a3f0fac0ee 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2959,3 +2959,15 @@ insert into attach_parted_part1 values (2, 1);
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..419e6fbc70 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,12 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +201,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +227,250 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
+
+-- Test the list of partitions published
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v43-0004-Add-documentation-for-the-schema-publication-fea.patchapplication/x-patch; name=v43-0004-Add-documentation-for-the-schema-publication-fea.patchDownload
From 3c129e1caf17c4c9d4df0bbd39b2a471d3f47fbe Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v43 4/4] Add documentation for the schema publication feature
of logical replication
The following schema publication documentation is added:
* Create/alter publication syntax, with examples
* pg_publication_namespace system table
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 +++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 77 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 67 ++++++++++++++++++---
3 files changed, 190 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..7bf132f8cd 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6176,6 +6181,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-publication-rel">
<title><structname>pg_publication_rel</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..2f38a69cf8 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> and <literal>DROP</literal> clauses will add and
+ remove one or more tables/schemas from the publication. Note that adding
+ tables/schemas to a publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +69,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +114,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +168,25 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add schemas <structname>marketing</structname> and
+ <structname>sales</structname> to the publication
+ <structname>sales_publication</structname>:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing, sales;
+</programlisting>
+ </para>
+
+ <para>
+ Add tables <structname>users</structname>,
+ <structname>departments</structname> and schema
+ <structname>production</structname> to the publication
+ <structname>production_publication</structname>:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..2c6bdfbac0 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication_object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,25 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables
+ <structname>users</structname>, <structname>departments</structname> and
+ that publishes all changes for all the tables present in the schema
+ <structname>production</structname>:
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas <structname>marketing</structname> and
+ <structname>sales</structname>:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
--
2.30.2
v43-0005-Add-new-pg_publication_objects-view-to-display-T.patchapplication/x-patch; name=v43-0005-Add-new-pg_publication_objects-view-to-display-T.patchDownload
From 04b4f50256c411989761fa30163d79f446a4647f Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v43 5/5] Add new "pg_publication_objects" view to display
"TABLE"/"SCHEMA" publication objects
A new "pg_publication_objects" view is added, to display table/schema object
information associated with publications.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 19 ++++++++
src/test/regress/expected/rules.out | 15 ++++++
3 files changed, 104 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 92de24f6de..a078302ac8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..f70348e34f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,25 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..8796f71de2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,21 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM ((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Tuesday, October 19, 2021 11:42 PM vignesh C <vignesh21@gmail.com> wrote:
This issue got induced in the v42 version, attached v43 patch has the
fixes for the same.
Thanks for your new patch. I confirmed that this issue has be fixed.
All regression tests passed.
I also tested V43 in some other scenarios and found no issue.
So the v43 patch LGTM.
Regards
Tang
On Tue, Oct 19, 2021 at 9:42 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the modified patch. I have a few more comments and suggestions:
As the thread [1]/messages/by-id/OS0PR01MB57167F45D481F78CDC5986F794B99@OS0PR01MB5716.jpnprd01.prod.outlook.com is still not concluded, I suggest we fix the
duplicate data case only when schemas are involved by slightly
tweaking the code as per attached. This is just to give you an idea
about what I have in mind, if you find a better solution then feel
free to let me know.
Few additional minor comments:
1.
+-- Test the list of partitions published
Shall we change the above comment as: "Test the list of partitions
published with or without 'PUBLISH_VIA_PARTITION_ROOT' parameter"?
2. psql documentation for \dRp[+] needs to be modified.
\dRp
Before:
" .. If + is appended to the command name, the tables associated with
each publication are shown as well."
After:
" .. If + is appended to the command name, the tables and schemas
associated with each publication are shown as well.
Apart from the above, I think we should merge the first four patches
as there doesn't seem to be any big problems pending. We can keep
still keep tests added by 025_rep_changes_for_schema.pl as a separate
patch as there might be some timing-dependent tests in that file.
[1]: /messages/by-id/OS0PR01MB57167F45D481F78CDC5986F794B99@OS0PR01MB5716.jpnprd01.prod.outlook.com
--
With Regards,
Amit Kapila.
Attachments:
change_partitions_schema_interaction_1.patchapplication/octet-stream; name=change_partitions_schema_interaction_1.patchDownload
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 8e268295f9..ca33432528 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -865,18 +865,28 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
- tables = list_concat_unique_oid(relids, schemarelids);
-
- /*
- * If the publication publishes partition changes via their
- * respective root partitioned tables, we must exclude partitions
- * in favor of including the root partitioned tables. Otherwise,
- * the function could return both the child and parent tables which
- * could cause data of the child table to be double-published on the
- * subscriber side.
- */
- if (publication->pubviaroot)
- tables = filter_partitions(tables);
+ if (schemarelids)
+ {
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude
+ * partitions in favor of including the root partitioned
+ * tables. Otherwise, the function could return both the child
+ * and parent tables which could cause data of the child table
+ * to be double-published on the subscriber side.
+ *
+ * XXX As of now, we do this when a publication has associated
+ * schema or for all tables publication. See
+ * GetAllTablesPublicationRelations().
+ */
+ tables = list_concat_unique_oid(relids, schemarelids);
+ if (publication->pubviaroot)
+ tables = filter_partitions(tables);
+ }
+ else
+ {
+ tables = relids;
+ }
}
funcctx->user_fctx = (void *) tables;
On Wed, Oct 20, 2021 at 12:32 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Oct 19, 2021 at 9:42 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the modified patch. I have a few more comments and suggestions:
As the thread [1] is still not concluded, I suggest we fix the
duplicate data case only when schemas are involved by slightly
tweaking the code as per attached. This is just to give you an idea
about what I have in mind, if you find a better solution then feel
free to let me know.Few additional minor comments:
1.
+-- Test the list of partitions publishedShall we change the above comment as: "Test the list of partitions
published with or without 'PUBLISH_VIA_PARTITION_ROOT' parameter"?
Modified
2. psql documentation for \dRp[+] needs to be modified.
\dRp
Before:
" .. If + is appended to the command name, the tables associated with
each publication are shown as well."
After:
" .. If + is appended to the command name, the tables and schemas
associated with each publication are shown as well.
Modified
Apart from the above, I think we should merge the first four patches
as there doesn't seem to be any big problems pending. We can keep
still keep tests added by 025_rep_changes_for_schema.pl as a separate
patch as there might be some timing-dependent tests in that file.
This version of patch retains the changes related to
PublicationRelInfo, I will handle the merging of the patches in the
next version so that this version of patch change related to
PublicationRelInfo can be reviewed easily.
Attached v44 patch as the fixes for the same.
Regards,
Vignesh
Attachments:
v44-0001-Allow-publishing-the-tables-of-schema.patchtext/x-patch; charset=UTF-8; name=v44-0001-Allow-publishing-the-tables-of-schema.patchDownload
From f2a0af132a8fbcd9cae94bce713b076e4a95747e Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Mon, 18 Oct 2021 14:07:14 +0800
Subject: [PATCH v44 1/5] Allow publishing the tables of schema.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A new option "FOR ALL TABLES IN SCHEMA" in Create/Alter Publication allows
one or more schemas to be specified, whose tables are selected by the
publisher for sending the data to the subscriber.
The new syntax allows specifying both the tables and schemas. For example:
CREATE PUBLICATION pub1 FOR TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2;
OR
ALTER PUBLICATION pub1 ADD TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2;
A new system table "pg_publication_namespace" has been added, to maintain
the schemas that the user wants to publish through the publication.
Modified the output plugin (pgoutput) to publish the changes if the
relation is part of schema publication.
CATALOG_VERSION_NO needs to be updated while committing, as this feature involves a catalog change.
Author: Vignesh C, Hou Zhijie, Amit Kapila
Syntax-by: Tom Lane, Álvaro Herrera, Peter Eisentraut
Reviewed-by: Greg Nancarrow, Masahiko Sawada, Hou Zhijie, Amit Kapila, Haiying Tang, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Haiying Tang
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 ++++++
src/backend/catalog/pg_publication.c | 329 +++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 472 ++++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 ++
src/backend/nodes/copyfuncs.c | 22 +-
src/backend/nodes/equalfuncs.c | 21 +-
src/backend/parser/gram.y | 307 +++++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 11 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 36 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/sanity_check.out | 1 +
src/tools/pgindent/typedefs.list | 4 +
26 files changed, 1384 insertions(+), 120 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..b67c95b9ae 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -76,6 +78,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -105,6 +131,45 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
relid >= FirstNormalObjectId;
}
+/*
+ * Filter out the partitions whose parent tables were also specified in
+ * the publication.
+ */
+static List *
+filter_partitions(List *relids)
+{
+ List *result = NIL;
+ ListCell *lc;
+ ListCell *lc2;
+
+ foreach(lc, relids)
+ {
+ bool skip = false;
+ List *ancestors = NIL;
+ Oid relid = lfirst_oid(lc);
+
+ if (get_rel_relispartition(relid))
+ ancestors = get_partition_ancestors(relid);
+
+ foreach(lc2, ancestors)
+ {
+ /*
+ * Check if the parent table exists in the published table list.
+ */
+ if (list_member_oid(relids, lfirst_oid(lc2)))
+ {
+ skip = true;
+ break;
+ }
+ }
+
+ if (!skip)
+ result = lappend_oid(result, relid);
+ }
+
+ return result;
+}
+
/*
* Another variant of this, taking a Relation.
*/
@@ -262,6 +327,89 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +576,151 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[1];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the specified schema */
+ scan = table_beginscan_catalog(classRel, 1, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+ char relkind;
+
+ if (!is_publishable_class(relid, relForm))
+ continue;
+
+ relkind = get_rel_relkind(relid);
+ if (relkind == RELKIND_RELATION)
+ result = lappend_oid(result, relid);
+ else if (relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *partitionrels = NIL;
+
+ /*
+ * It is quite possible that some of the partitions are in a
+ * different schema than the parent table, so we need to get such
+ * partitions separately.
+ */
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(pubid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +848,46 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ if (schemarelids)
+ {
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude
+ * partitions in favor of including the root partitioned
+ * tables. Otherwise, the function could return both the child
+ * and parent tables which could cause data of the child table
+ * to be double-published on the subscriber side.
+ *
+ * XXX As of now, we do this when a publication has associated
+ * schema or for all tables publication. See
+ * GetAllTablesPublicationRelations().
+ */
+ tables = list_concat_unique_oid(relids, schemarelids);
+ if (publication->pubviaroot)
+ tables = filter_partitions(tables);
+ }
+ else
+ tables = relids;
+
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..0e23028c94 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,6 +36,7 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -45,11 +48,16 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,97 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->pubtable);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
+ Relation rel = pub_rel->relation;
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +251,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +322,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (list_length(schemaidlist) > 0 && !superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +442,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +491,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +529,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -440,11 +581,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +717,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +817,90 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ PublicationRelInfo *pub_rel;
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ pub_rel = palloc(sizeof(PublicationRelInfo));
+ pub_rel->relation = rel;
+ rels = lappend(rels, pub_rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -692,6 +1038,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -727,6 +1101,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 308e0adb55..53c18628a7 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1a2f159f24..857cc5ce6e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12286,6 +12286,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15994,6 +15995,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..f3606bfd81 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4820,6 +4820,19 @@ _copyPublicationTable(const PublicationTable *from)
return newnode;
}
+static PublicationObjSpec*
+_copyPublicationObject(const PublicationObjSpec *from)
+{
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
+
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(pubtable);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static CreatePublicationStmt *
_copyCreatePublicationStmt(const CreatePublicationStmt *from)
{
@@ -4827,7 +4840,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4853,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5890,6 +5903,9 @@ copyObjectImpl(const void *from)
case T_PublicationTable:
retval = _copyPublicationTable(from);
break;
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..abd827ed79 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2310,7 +2310,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2322,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3046,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec* a,
+ const PublicationObjSpec* b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(pubtable);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3897,6 +3909,9 @@ equal(const void *a, const void *b)
case T_PublicationTable:
retval = _equalPublicationTable(a, b);
break;
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..871fa20c93 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,131 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->name = $1;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = $1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9734,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12502,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15183,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17118,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17305,74 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or rangevar must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubtable)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to rangevar */
+ PublicationTable *pubtable = makeNode(PublicationTable);
+ pubtable->relation = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->pubtable = pubtable;
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and rangevar is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->pubtable)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..f816d14b4d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -111,13 +111,22 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid,
+ PublicationRelInfo *targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..c9b55a66f3 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,6 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
+ T_PublicationObjSpec,
T_PublicationTable,
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..42eacd530f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -353,6 +353,29 @@ typedef struct RoleSpec
int location; /* token location, or -1 if unknown */
} RoleSpec;
+/* forward declaration */
+struct PublicationTable;
+
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ struct PublicationTable *pubtable;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
/*
* FuncCall - a function or aggregate invocation
*
@@ -1816,6 +1839,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3647,7 +3671,7 @@ typedef struct CreatePublicationStmt
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3683,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..4d9a9f0d4a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,6 +2048,8 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationTable
--
2.30.2
v44-0002-Add-client-side-support-to-logical-replication-f.patchtext/x-patch; charset=US-ASCII; name=v44-0002-Add-client-side-support-to-logical-replication-f.patchDownload
From 47931f801b31a8df4eec36eaa8ffb8810d8cd628 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 20 Oct 2021 21:10:15 +0530
Subject: [PATCH v44 2/5] Add client-side support to logical replication for
publishing the tables of a schema.
To add client-side support to logical replication for publishing the tables of
a schema, the following are added:
Updates pg_dump to identify and dump schema publications. Updates the \d
family of commands to display schema publications and \dRp+ variant will
now display associated schemas if any.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 14 ++
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/psql/describe.c | 198 +++++++++++++++++++++------
src/bin/psql/tab-complete.c | 40 +++++-
src/tools/pgindent/typedefs.list | 1 +
8 files changed, 358 insertions(+), 54 deletions(-)
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ed8ed2f266..dd1a1f4868 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10187,6 +10314,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18419,6 +18550,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c..c7f97476d1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3146,17 +3146,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5020,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5052,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6209,6 +6297,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6286,15 +6412,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6327,6 +6448,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6336,31 +6458,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6373,6 +6486,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..7d53379a2c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,22 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2700,31 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 4d9a9f0d4a..a393492feb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2052,6 +2052,7 @@ PublicationObjSpec
PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PublicationTable
PullFilter
PullFilterOps
--
2.30.2
v44-0003-Add-tests-for-the-schema-publication-feature-of-.patchtext/x-patch; charset=US-ASCII; name=v44-0003-Add-tests-for-the-schema-publication-feature-of-.patchDownload
From 75b87f250917054297cd9d1996ef5248e3877f83 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 19 Oct 2021 17:20:51 +0530
Subject: [PATCH v44 3/5] Add tests for the schema publication feature of
logical replication
Schema publication tests are added to verify the following:
* Invalidation
* Add/Drop/Set ALL TABLES IN SCHEMA
* Schema publication handling in pg_dump
* Replication of schema publications
* psql \d display of publications and tables
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/publication.out | 522 +++++++++++++++++-
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 281 +++++++++-
.../t/025_rep_changes_for_schema.pl | 168 ++++++
8 files changed, 1031 insertions(+), 5 deletions(-)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c3b2b37067..24d1c7cd28 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4557,3 +4557,17 @@ DETAIL: Failing row contains (2, 1).
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..0f4fe4db8f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,23 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to create FOR ALL TABLES IN SCHEMA publication
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +390,452 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
+-- Test the list of partitions published with or without
+-- 'PUBLISH_VIA_PARTITION_ROOT' parameter
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+-----------
+ pub | sch1 | tbl1
+(1 row)
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 4ddbd16a4e..5fac2585d9 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2990,3 +2990,15 @@ insert into attach_parted_part1 values (2, 1);
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..85a5302a74 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,12 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +201,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +227,251 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
+
+-- Test the list of partitions published with or without
+-- 'PUBLISH_VIA_PARTITION_ROOT' parameter
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v44-0004-Add-documentation-for-the-schema-publication-fea.patchtext/x-patch; charset=US-ASCII; name=v44-0004-Add-documentation-for-the-schema-publication-fea.patchDownload
From fc650e556c9ee3ae37888af6af5cc3613819292b Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 3 Sep 2021 17:46:08 +0530
Subject: [PATCH v44 4/5] Add documentation for the schema publication feature
of logical replication
The following schema publication documentation is added:
* Create/alter publication syntax, with examples
* pg_publication_namespace system table
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 +++++++++++++++++++++-
doc/src/sgml/ref/alter_publication.sgml | 77 +++++++++++++++++++-----
doc/src/sgml/ref/create_publication.sgml | 67 ++++++++++++++++++---
doc/src/sgml/ref/psql-ref.sgml | 4 +-
4 files changed, 192 insertions(+), 28 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..7bf132f8cd 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6176,6 +6181,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-publication-rel">
<title><structname>pg_publication_rel</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..2f38a69cf8 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> and <literal>DROP</literal> clauses will add and
+ remove one or more tables/schemas from the publication. Note that adding
+ tables/schemas to a publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +69,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +114,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +168,25 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add schemas <structname>marketing</structname> and
+ <structname>sales</structname> to the publication
+ <structname>sales_publication</structname>:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing, sales;
+</programlisting>
+ </para>
+
+ <para>
+ Add tables <structname>users</structname>,
+ <structname>departments</structname> and schema
+ <structname>production</structname> to the publication
+ <structname>production_publication</structname>:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..2c6bdfbac0 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication_object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,28 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +185,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +204,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +256,25 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables
+ <structname>users</structname>, <structname>departments</structname> and
+ that publishes all changes for all the tables present in the schema
+ <structname>production</structname>:
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas <structname>marketing</structname> and
+ <structname>sales</structname>:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 14e0a4dbe3..48248f750e 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1853,8 +1853,8 @@ testdb=>
If <replaceable class="parameter">pattern</replaceable> is
specified, only those publications whose names match the pattern are
listed.
- If <literal>+</literal> is appended to the command name, the tables
- associated with each publication are shown as well.
+ If <literal>+</literal> is appended to the command name, the tables and
+ schemas associated with each publication are shown as well.
</para>
</listitem>
</varlistentry>
--
2.30.2
v44-0005-Add-new-pg_publication_objects-view-to-display-T.patchtext/x-patch; charset=US-ASCII; name=v44-0005-Add-new-pg_publication_objects-view-to-display-T.patchDownload
From df6670d55230097be3f36c26ff9181c98846c06d Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v44 5/5] Add new "pg_publication_objects" view to display
"TABLE"/"SCHEMA" publication objects
A new "pg_publication_objects" view is added, to display table/schema object
information associated with publications.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 19 ++++++++
src/test/regress/expected/rules.out | 15 ++++++
3 files changed, 104 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 7bf132f8cd..a85053ab0b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..f70348e34f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,25 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..8796f71de2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,21 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM ((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Thu, Oct 21, 2021 at 3:25 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v44 patch as the fixes for the same.
Regarding the documentation, I think some minor updates are needed in:
doc/src/sgml/logical-replication.sgml.
For example, currently it says:
Publications may currently only contain tables. Objects must be
added explicitly, except when a publication is created for
<literal>ALL TABLES</literal>.
There is also some security-related information in this file which may
need updating for ALL TABLES IN SCHEMA.
Also, I'm not sure the documentation updates in the patches clearly
define how partitions relate to ALL TABLES IN SCHEMA.
For example, if a partitioned table belongs to a different schema to
that of a child partition that belongs to a schema specified for ALL
TABLES IN SCHEMA, is it implicitly included in the publication or not
included?
Regards,
Greg Nancarrow
Fujitsu Australia
On Thurs, Oct 21, 2021 12:25 AM vignesh C <vignesh21@gmail.com> wrote:
This version of patch retains the changes related to PublicationRelInfo, I will
handle the merging of the patches in the next version so that this version of
patch change related to PublicationRelInfo can be reviewed easily.
Thanks for the patches,
I think the change related to PublicationRelInfo looks good.
Best regards,
Hou zj
On Thu, Oct 21, 2021 at 3:25 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v44 patch as the fixes for the same.
In the v44-0001 patch, I have some doubts about the condition guarding
the following code in pg_get_publication_tables():
+ if (schemarelids)
+ {
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude
+ * partitions in favor of including the root partitioned
+ * tables. Otherwise, the function could return both the child
+ * and parent tables which could cause data of the child table
+ * to be double-published on the subscriber side.
+ *
+ * XXX As of now, we do this when a publication has associated
+ * schema or for all tables publication. See
+ * GetAllTablesPublicationRelations().
+ */
+ tables = list_concat_unique_oid(relids, schemarelids);
+ if (publication->pubviaroot)
+ tables = filter_partitions(tables);
+ }
Shouldn't a partition be filtered out only if publication->pubviaroot
and the partition belongs to a schema (i.e. ALL TABLES IN SCHEMA)
included in the publication?
The current code seems to filter out partitions of partitioned tables
included in the publication if ANY schemas are included as part of the
publication (e.g. which could be a schema not including any
partitioned tables or partitions).
Regards,
Greg Nancarrow
Fujitsu Australia
On Thu, Oct 21, 2021 at 4:41 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Thu, Oct 21, 2021 at 3:25 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v44 patch as the fixes for the same.
Regarding the documentation, I think some minor updates are needed in:
doc/src/sgml/logical-replication.sgml.
For example, currently it says:
Publications may currently only contain tables. Objects must be
added explicitly, except when a publication is created for
<literal>ALL TABLES</literal>.
There is also some security-related information in this file which may
need updating for ALL TABLES IN SCHEMA.
Modified
Also, I'm not sure the documentation updates in the patches clearly
define how partitions relate to ALL TABLES IN SCHEMA.
For example, if a partitioned table belongs to a different schema to
that of a child partition that belongs to a schema specified for ALL
TABLES IN SCHEMA, is it implicitly included in the publication or not
included?
Added
Thanks for the comments, the attached v45 patch has the fix for the same.
Regards,
Vignesh
Attachments:
v45-0001-Allow-publishing-the-tables-of-schema.patchtext/x-patch; charset=UTF-8; name=v45-0001-Allow-publishing-the-tables-of-schema.patchDownload
From 5b90215c79ad37b6798d19577b4945f4bf8683aa Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Mon, 18 Oct 2021 14:07:14 +0800
Subject: [PATCH v45 1/3] Allow publishing the tables of schema.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A new option "FOR ALL TABLES IN SCHEMA" in Create/Alter Publication allows
one or more schemas to be specified, whose tables are selected by the
publisher for sending the data to the subscriber.
The new syntax allows specifying both the tables and schemas. For example:
CREATE PUBLICATION pub1 FOR TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2;
OR
ALTER PUBLICATION pub1 ADD TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2;
A new system table "pg_publication_namespace" has been added, to maintain
the schemas that the user wants to publish through the publication.
Modified the output plugin (pgoutput) to publish the changes if the
relation is part of schema publication.
Updates pg_dump to identify and dump schema publications. Updates the \d
family of commands to display schema publications and \dRp+ variant will
now display associated schemas if any.
CATALOG_VERSION_NO needs to be updated while committing, as this feature involves a catalog change.
Author: Vignesh C, Hou Zhijie, Amit Kapila
Syntax-by: Tom Lane, Álvaro Herrera, Peter Eisentraut
Reviewed-by: Greg Nancarrow, Masahiko Sawada, Hou Zhijie, Amit Kapila, Haiying Tang, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Haiying Tang
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 ++-
doc/src/sgml/logical-replication.sgml | 14 +-
doc/src/sgml/ref/alter_publication.sgml | 77 ++-
doc/src/sgml/ref/create_publication.sgml | 76 ++-
doc/src/sgml/ref/psql-ref.sgml | 4 +-
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 337 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 472 ++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 22 +-
src/backend/nodes/equalfuncs.c | 21 +-
src/backend/parser/gram.y | 307 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++-
src/bin/pg_dump/pg_dump.h | 14 +
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/bin/psql/describe.c | 198 +++++--
src/bin/psql/tab-complete.c | 40 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 11 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 33 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 522 +++++++++++++++++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 281 +++++++++-
src/tools/pgindent/typedefs.list | 5 +
45 files changed, 2819 insertions(+), 213 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..7bf132f8cd 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6176,6 +6181,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-publication-rel">
<title><structname>pg_publication_rel</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 88646bc859..45b2e1e28f 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -108,9 +108,9 @@
<para>
Publications are different from schemas and do not affect how the table is
accessed. Each table can be added to multiple publications if needed.
- Publications may currently only contain tables. Objects must be added
- explicitly, except when a publication is created for <literal>ALL
- TABLES</literal>.
+ Publications may currently only contain tables and all tables in schema.
+ Objects must be added explicitly, except when a publication is created for
+ <literal>ALL TABLES</literal>.
</para>
<para>
@@ -534,7 +534,8 @@
and <literal>TRIGGER</literal> privilege on such tables to roles that
superusers trust. Moreover, if untrusted users can create tables, use only
publications that list tables explicitly. That is to say, create a
- subscription <literal>FOR ALL TABLES</literal> only when superusers trust
+ subscription <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> only when superusers trust
every user permitted to create a non-temp table on the publisher or the
subscriber.
</para>
@@ -564,8 +565,9 @@
<para>
To add tables to a publication, the user must have ownership rights on the
- table. To create a publication that publishes all tables automatically,
- the user must be a superuser.
+ table. To add all tables in schema to a publication, the user must be a
+ superuser. To create a publication that publishes all tables or all tables in
+ schema automatically, the user must be a superuser.
</para>
<para>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..2f38a69cf8 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> and <literal>DROP</literal> clauses will add and
+ remove one or more tables/schemas from the publication. Note that adding
+ tables/schemas to a publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +69,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
+ However, a superuser can change the ownership of a publication regardless
+ of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +114,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +168,25 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add schemas <structname>marketing</structname> and
+ <structname>sales</structname> to the publication
+ <structname>sales_publication</structname>:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing, sales;
+</programlisting>
+ </para>
+
+ <para>
+ Add tables <structname>users</structname>,
+ <structname>departments</structname> and schema
+ <structname>production</structname> to the publication
+ <structname>production_publication</structname>:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..61c4c074c4 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication_object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,37 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+
+ <para>
+ When a partitioned table is published via schema level publication, all
+ of its existing and future partitions irrespective of it being from the
+ publication schema or not are implicitly considered to be part of the
+ publication. So, even operations that are performed directly on a
+ partition are also published via publications that its ancestors are
+ part of.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +194,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +213,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +265,25 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables
+ <structname>users</structname>, <structname>departments</structname> and
+ that publishes all changes for all the tables present in the schema
+ <structname>production</structname>:
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas <structname>marketing</structname> and
+ <structname>sales</structname>:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 14e0a4dbe3..48248f750e 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1853,8 +1853,8 @@ testdb=>
If <replaceable class="parameter">pattern</replaceable> is
specified, only those publications whose names match the pattern are
listed.
- If <literal>+</literal> is appended to the command name, the tables
- associated with each publication are shown as well.
+ If <literal>+</literal> is appended to the command name, the tables and
+ schemas associated with each publication are shown as well.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..2f552728f5 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -76,6 +78,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -105,6 +131,53 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
relid >= FirstNormalObjectId;
}
+/*
+ * Filter out the partitions whose parent tables were also specified in
+ * the publication.
+ */
+static List *
+filter_partitions(List *relids, List *schemarelids)
+{
+ List *result = NIL;
+ ListCell *lc;
+ ListCell *lc2;
+
+ foreach(lc, relids)
+ {
+ bool skip = false;
+ List *ancestors = NIL;
+ Oid relid = lfirst_oid(lc);
+
+ if (get_rel_relispartition(relid))
+ ancestors = get_partition_ancestors(relid);
+
+ foreach(lc2, ancestors)
+ {
+ Oid ancestor = lfirst_oid(lc2);
+
+ /*
+ * Check if the parent table exists in the published table list.
+ *
+ * XXX As of now, we do this if the partition relation or the
+ * partition relation's ancestor is present in schema publication
+ * relations.
+ */
+ if (list_member_oid(relids, ancestor) &&
+ (list_member_oid(schemarelids, relid) ||
+ list_member_oid(schemarelids, ancestor)))
+ {
+ skip = true;
+ break;
+ }
+ }
+
+ if (!skip)
+ result = lappend_oid(result, relid);
+ }
+
+ return result;
+}
+
/*
* Another variant of this, taking a Relation.
*/
@@ -262,6 +335,89 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +584,151 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[1];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the specified schema */
+ scan = table_beginscan_catalog(classRel, 1, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+ char relkind;
+
+ if (!is_publishable_class(relid, relForm))
+ continue;
+
+ relkind = get_rel_relkind(relid);
+ if (relkind == RELKIND_RELATION)
+ result = lappend_oid(result, relid);
+ else if (relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *partitionrels = NIL;
+
+ /*
+ * It is quite possible that some of the partitions are in a
+ * different schema than the parent table, so we need to get such
+ * partitions separately.
+ */
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(pubid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +856,46 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+
+ if (schemarelids)
+ {
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude
+ * partitions in favor of including the root partitioned
+ * tables. Otherwise, the function could return both the child
+ * and parent tables which could cause data of the child table
+ * to be double-published on the subscriber side.
+ *
+ * XXX As of now, we do this when a publication has associated
+ * schema or for all tables publication. See
+ * GetAllTablesPublicationRelations().
+ */
+ tables = list_concat_unique_oid(relids, schemarelids);
+ if (publication->pubviaroot)
+ tables = filter_partitions(tables, schemarelids);
+ }
+ else
+ tables = relids;
+
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..0e23028c94 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,6 +36,7 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -45,11 +48,16 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,97 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->pubtable);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
+ Relation rel = pub_rel->relation;
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +251,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +322,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (list_length(schemaidlist) > 0 && !superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +442,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +491,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +529,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -440,11 +581,113 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +717,29 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -552,9 +817,90 @@ RemovePublicationById(Oid pubid)
}
/*
- * Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+ }
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ PublicationRelInfo *pub_rel;
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ pub_rel = palloc(sizeof(PublicationRelInfo));
+ pub_rel->relation = rel;
+ rels = lappend(rels, pub_rel);
+ }
+
+ return rels;
+}
+
+/*
+ * Open relations specified by a RangeVar list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -692,6 +1038,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -727,6 +1101,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 308e0adb55..53c18628a7 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1a2f159f24..857cc5ce6e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12286,6 +12286,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15994,6 +15995,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..ef7cf20738 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4820,6 +4820,19 @@ _copyPublicationTable(const PublicationTable *from)
return newnode;
}
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
+{
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
+
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(pubtable);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static CreatePublicationStmt *
_copyCreatePublicationStmt(const CreatePublicationStmt *from)
{
@@ -4827,7 +4840,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4853,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5890,6 +5903,9 @@ copyObjectImpl(const void *from)
case T_PublicationTable:
retval = _copyPublicationTable(from);
break;
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..8d0e4a0f25 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2310,7 +2310,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2322,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3046,6 +3046,18 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(pubtable);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3897,6 +3909,9 @@ equal(const void *a, const void *b)
case T_PublicationTable:
retval = _equalPublicationTable(a, b);
break;
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..03691bf404 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,131 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->name = $1;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = $1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * pub_obj is one of:
+ *
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9734,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12502,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15183,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17118,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17305,74 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or pubtable must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubtable)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to PublicationTable */
+ PublicationTable *pubtable = makeNode(PublicationTable);
+ pubtable->relation = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->pubtable = pubtable;
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and pubtable is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->pubtable)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ed8ed2f266..dd1a1f4868 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10187,6 +10314,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18419,6 +18550,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c..c7f97476d1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3146,17 +3146,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5020,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5052,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6209,6 +6297,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6286,15 +6412,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6327,6 +6448,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6336,31 +6458,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6373,6 +6486,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..8e01f54500 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,22 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2700,31 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..f816d14b4d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -111,13 +111,22 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid,
+ PublicationRelInfo *targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..c9b55a66f3 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,6 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
+ T_PublicationObjSpec,
T_PublicationTable,
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..49123e28a4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1816,6 +1816,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3642,12 +3643,32 @@ typedef struct PublicationTable
RangeVar *relation; /* relation to be published */
} PublicationTable;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ PublicationTable *pubtable;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3680,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c3b2b37067..24d1c7cd28 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4557,3 +4557,17 @@ DETAIL: Failing row contains (2, 1).
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..0f4fe4db8f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,23 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to create FOR ALL TABLES IN SCHEMA publication
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +390,452 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
+-- Test the list of partitions published with or without
+-- 'PUBLISH_VIA_PARTITION_ROOT' parameter
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+-----------
+ pub | sch1 | tbl1
+(1 row)
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 4ddbd16a4e..5fac2585d9 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2990,3 +2990,15 @@ insert into attach_parted_part1 values (2, 1);
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..85a5302a74 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,12 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +201,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +227,251 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
+
+-- Test the list of partitions published with or without
+-- 'PUBLISH_VIA_PARTITION_ROOT' parameter
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..a393492feb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,8 +2048,11 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PublicationTable
PullFilter
PullFilterOps
--
2.30.2
v45-0002-Add-tap-tests-for-the-schema-publication-feature.patchtext/x-patch; charset=US-ASCII; name=v45-0002-Add-tap-tests-for-the-schema-publication-feature.patchDownload
From ff5a2d8b3e684a64ef061b422f817bda6afb884b Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 21 Oct 2021 14:25:24 +0530
Subject: [PATCH v45 2/3] Add tap tests for the schema publication feature of
logical replication
Add tap tests for the schema publication feature of logical replication
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
.../t/025_rep_changes_for_schema.pl | 168 ++++++++++++++++++
1 file changed, 168 insertions(+)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v45-0003-Add-new-pg_publication_objects-view-to-display-T.patchtext/x-patch; charset=US-ASCII; name=v45-0003-Add-new-pg_publication_objects-view-to-display-T.patchDownload
From 51e060f8ace66c054a2316e8b24b0e84100c8a4d Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v45 3/3] Add new "pg_publication_objects" view to display
"TABLE"/"SCHEMA" publication objects
A new "pg_publication_objects" view is added, to display table/schema object
information associated with publications.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 19 ++++++++
src/test/regress/expected/rules.out | 15 ++++++
3 files changed, 104 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 7bf132f8cd..a85053ab0b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..f70348e34f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,25 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..8796f71de2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,21 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM ((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Thu, Oct 21, 2021 at 3:29 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Thu, Oct 21, 2021 at 3:25 AM vignesh C <vignesh21@gmail.com> wrote:
Attached v44 patch as the fixes for the same.
In the v44-0001 patch, I have some doubts about the condition guarding
the following code in pg_get_publication_tables():+ if (schemarelids) + { + /* + * If the publication publishes partition changes via their + * respective root partitioned tables, we must exclude + * partitions in favor of including the root partitioned + * tables. Otherwise, the function could return both the child + * and parent tables which could cause data of the child table + * to be double-published on the subscriber side. + * + * XXX As of now, we do this when a publication has associated + * schema or for all tables publication. See + * GetAllTablesPublicationRelations(). + */ + tables = list_concat_unique_oid(relids, schemarelids); + if (publication->pubviaroot) + tables = filter_partitions(tables); + }Shouldn't a partition be filtered out only if publication->pubviaroot
and the partition belongs to a schema (i.e. ALL TABLES IN SCHEMA)
included in the publication?
The current code seems to filter out partitions of partitioned tables
included in the publication if ANY schemas are included as part of the
publication (e.g. which could be a schema not including any
partitioned tables or partitions).
I could reproduce the issue by using the following test:
--- Setup
create schema sch1;
create schema sch2;
create table sch1.tbl1 (a int) partition by range (a);
create table sch2.tbl1_part1 partition of sch1.tbl1 for values from (1) to (10);
create table sch2.tbl1_part2 partition of sch1.tbl1 for values from
(10) to (20);
create schema sch3;
create table sch3.t1(c1 int);
--- Publication
create publication pub1 for all tables in schema sch3, table
sch1.tbl1, table sch2.tbl1_part1 with ( publish_via_partition_root
=on);
insert into sch1.tbl1 values(1);
insert into sch1.tbl1 values(11);
insert into sch3.t1 values(1);
---- Subscription
CREATE SUBSCRIPTION sub CONNECTION 'dbname=postgres host=localhost
port=5432' PUBLICATION pub1;
The patch posted at [1]/messages/by-id/CALDaNm1onqBEr0WE_e7=CNw3bURfrGRmbMjX31d-nx3FGLS10A@mail.gmail.com has the fix for the same.
[1]: /messages/by-id/CALDaNm1onqBEr0WE_e7=CNw3bURfrGRmbMjX31d-nx3FGLS10A@mail.gmail.com
Regards,
Vignesh
On Fri, Oct 22, 2021 at 12:19 AM vignesh C <vignesh21@gmail.com> wrote:
I could reproduce the issue by using the following test: --- Setup create schema sch1; create schema sch2; create table sch1.tbl1 (a int) partition by range (a); create table sch2.tbl1_part1 partition of sch1.tbl1 for values from (1) to (10); create table sch2.tbl1_part2 partition of sch1.tbl1 for values from (10) to (20); create schema sch3; create table sch3.t1(c1 int);--- Publication create publication pub1 for all tables in schema sch3, table sch1.tbl1, table sch2.tbl1_part1 with ( publish_via_partition_root =on); insert into sch1.tbl1 values(1); insert into sch1.tbl1 values(11); insert into sch3.t1 values(1);---- Subscription
CREATE SUBSCRIPTION sub CONNECTION 'dbname=postgres host=localhost
port=5432' PUBLICATION pub1;The patch posted at [1] has the fix for the same.
[1] - /messages/by-id/CALDaNm1onqBEr0WE_e7=CNw3bURfrGRmbMjX31d-nx3FGLS10A@mail.gmail.com
Thanks for addressing this.
I checked the updated code and did some more testing and the fix LGTM.
I was also previously concerned about what the behavior should be when
only including just the partitions of a partitioned table in a
publication using ALL TABLES IN SCHEMA and having
publish_via_partition_root=true. It seems to implicitly include the
partitioned table in the publication. But I did some testing and found
that this is the current behavior when only the partitions are
individually included in a publication using TABLE, so it seems to be
OK.
Regards,
Greg Nancarrow
Fujitsu Australia
On Thu, Oct 21, 2021 at 6:47 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v45 patch has the fix for the same.
The first patch is mostly looking good to me apart from the below
minor comments:
1.
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
There are extra spaces after mapping at the end which are not required.
2.
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> publication must be a superuser.
I think we can modify the second line as: "Also, the new owner of a
<literal>FOR ALL TABLES</literal> or <literal>FOR ALL TABLES IN
SCHEMA</literal> publication must be a superuser.
3.
/table's schema as part of specified schema is not supported./table's
schema as part of the specified schema is not supported.
4.
+ <para>
+ Create a publication that publishes all changes for tables
+ <structname>users</structname>, <structname>departments</structname> and
+ that publishes all changes for all the tables present in the schema
+ <structname>production</structname>:
I don't think '...that publishes...' is required twice in the above sentence.
5.
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void LockSchemaList(List *schemalist);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
Keep the later definitions also in this order. I suggest move
LockSchemaList() just after CloseTableList() both in declaration and
definition.
6.
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and rangevar
+ * list.
+ */
I think you need to say publication table instead of rangevar in the
above comment.
7.
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schema in which case we need to remove all the existing schemas.
+ */
/schema/schemas
8.
+/*
+ * Open relations specified by a RangeVar list.
/RangeVar/PublicationTable
9.
+static bool
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(pubtable);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
Let's define this immediately before _equalPublicationTable as all
publication functions are defined there. Also, make the handling of
T_PublicationObjSpec before T_PublicationTable in equal() function as
that is the way nodes are defined.
--
With Regards,
Amit Kapila.
On Fri, Oct 22, 2021 at 12:41 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
I was also previously concerned about what the behavior should be when
only including just the partitions of a partitioned table in a
publication using ALL TABLES IN SCHEMA and having
publish_via_partition_root=true. It seems to implicitly include the
partitioned table in the publication. But I did some testing and found
that this is the current behavior when only the partitions are
individually included in a publication using TABLE, so it seems to be
OK.
Thinking some more about this, it still may still be confusing to the
user if not explicitly stated in the ALL TABLES IN SCHEMA case.
How about adding some additional explanation to the end of the
following paragraph:
+ <para>
+ When a partitioned table is published via schema level publication, all
+ of its existing and future partitions irrespective of it being from the
+ publication schema or not are implicitly considered to be part of the
+ publication. So, even operations that are performed directly on a
+ partition are also published via publications that its ancestors are
+ part of.
+ </para>
Something like:
Similarly, if a partition is published via schema level publication
and publish_via_partition_root=true, the parent partitioned table is
implicitly considered to be part of the publication, irrespective of
it being from the publication schema or not.
Regards,
Greg Nancarrow
Fujitsu Australia
On Fri, Oct 22, 2021 at 2:25 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Oct 21, 2021 at 6:47 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v45 patch has the fix for the same.
The first patch is mostly looking good to me apart from the below
minor comments:
Let me share other minor comments on v45-0001 patch:
1. + <para> + The catalog <structname>pg_publication_namespace</structname> contains the + mapping between schemas and publications in the database. This is a + many-to-many mapping.There are extra spaces after mapping at the end which are not required.
+ <literal>ADD</literal> and <literal>DROP</literal> clauses will add and
+ remove one or more tables/schemas from the publication. Note that adding
+ tables/schemas to a publication that is already subscribed to will require a
There is also an extra space after "adding".
- [ FOR TABLE [ ONLY ] <replaceable
class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable
class="parameter">publication_object</replaceable> [, ... ] ]
Similarly, after "TABLES".
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
And, after "by".
---
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List
*schemaidlist)
+{
(snip)
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+
+ return;
+}
The "return" at the end of the function is not necessary.
---
+ if (pubobj->name)
+ pubobj->pubobjtype =
PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->pubtable)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else if (!pubobj->name)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid
schema name at or near"),
+
parser_errposition(pubobj->location));
I think it's better to change the last "else if" to just "else".
---
+
+ if (schemarelids)
+ {
+ /*
+ * If the publication publishes
partition changes via their
+ * respective root partitioned
tables, we must exclude
+ * partitions in favor of including
the root partitioned
+ * tables. Otherwise, the function
could return both the child
+ * and parent tables which could
cause data of the child table
+ * to be double-published on the
subscriber side.
+ *
+ * XXX As of now, we do this when a
publication has associated
+ * schema or for all tables publication. See
+ * GetAllTablesPublicationRelations().
+ */
+ tables =
list_concat_unique_oid(relids, schemarelids);
+ if (publication->pubviaroot)
+ tables =
filter_partitions(tables, schemarelids);
+ }
+ else
+ tables = relids;
+
+ }
There is an extra newline after "table = relids;".
The rest looks good to me.
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Fri, Oct 22, 2021 at 10:55 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Oct 21, 2021 at 6:47 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v45 patch has the fix for the same.
The first patch is mostly looking good to me apart from the below
minor comments:1. + <para> + The catalog <structname>pg_publication_namespace</structname> contains the + mapping between schemas and publications in the database. This is a + many-to-many mapping.There are extra spaces after mapping at the end which are not required.
Modified
2. + <literal>CREATE</literal> privilege on the database. Also, the new owner + of a <literal>FOR ALL TABLES</literal> publication must be a superuser.I think we can modify the second line as: "Also, the new owner of a
<literal>FOR ALL TABLES</literal> or <literal>FOR ALL TABLES IN
SCHEMA</literal> publication must be a superuser.
Modified
3.
/table's schema as part of specified schema is not supported./table's
schema as part of the specified schema is not supported.
Modified
4. + <para> + Create a publication that publishes all changes for tables + <structname>users</structname>, <structname>departments</structname> and + that publishes all changes for all the tables present in the schema + <structname>production</structname>:I don't think '...that publishes...' is required twice in the above sentence.
Modified
5. +static List *OpenReliIdList(List *relids); static List *OpenTableList(List *tables); static void CloseTableList(List *rels); static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists, AlterPublicationStmt *stmt); static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok); +static void LockSchemaList(List *schemalist); +static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists, + AlterPublicationStmt *stmt); +static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);Keep the later definitions also in this order. I suggest move
LockSchemaList() just after CloseTableList() both in declaration and
definition.
Modified
6. +/* + * Convert the PublicationObjSpecType list into schema oid list and rangevar + * list. + */I think you need to say publication table instead of rangevar in the
above comment.
Modified
7. + /* + * It is quite possible that for the SET case user has not specified any + * schema in which case we need to remove all the existing schemas. + *//schema/schemas
Modified
8. +/* + * Open relations specified by a RangeVar list./RangeVar/PublicationTable
Modified
9. +static bool +_equalPublicationObject(const PublicationObjSpec *a, + const PublicationObjSpec *b) +{ + COMPARE_SCALAR_FIELD(pubobjtype); + COMPARE_STRING_FIELD(name); + COMPARE_NODE_FIELD(pubtable); + COMPARE_LOCATION_FIELD(location); + + return true; +} +Let's define this immediately before _equalPublicationTable as all
publication functions are defined there. Also, make the handling of
T_PublicationObjSpec before T_PublicationTable in equal() function as
that is the way nodes are defined.
Modified
Thanks for the comments, attached v46 patch has the fix for the same.
Regards,
Vignesh
Attachments:
v46-0001-Allow-publishing-the-tables-of-schema.patchtext/x-patch; charset=US-ASCII; name=v46-0001-Allow-publishing-the-tables-of-schema.patchDownload
From 80f582040764fe346782d668f3cc27ef38962927 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Mon, 18 Oct 2021 14:07:14 +0800
Subject: [PATCH v46 1/3] Allow publishing the tables of schema.
A new option "FOR ALL TABLES IN SCHEMA" in Create/Alter Publication allows
one or more schemas to be specified, whose tables are selected by the
publisher for sending the data to the subscriber.
The new syntax allows specifying both the tables and schemas. For example:
CREATE PUBLICATION pub1 FOR TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2;
OR
ALTER PUBLICATION pub1 ADD TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2;
A new system table "pg_publication_namespace" has been added, to maintain
the schemas that the user wants to publish through the publication.
Modified the output plugin (pgoutput) to publish the changes if the
relation is part of schema publication.
Updates pg_dump to identify and dump schema publications. Updates the \d
family of commands to display schema publications and \dRp+ variant will
now display associated schemas if any.
CATALOG_VERSION_NO needs to be updated while committing, as this feature involves a catalog change.
Author: Vignesh C, Hou Zhijie, Amit Kapila
Syntax-by: Tom Lane, Alvaro Herrera, Peter Eisentraut
Reviewed-by Greg Nancarrow, Masahiko Sawada, Hou Zhijie, Amit Kapila, Haiying Tang, Ajin Cherian, Rahila Syed, Bharath Rupireddy, Mark Dilger
Tested-by: Haiying Tang
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ@mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 ++-
doc/src/sgml/logical-replication.sgml | 14 +-
doc/src/sgml/ref/alter_publication.sgml | 77 ++-
doc/src/sgml/ref/create_publication.sgml | 79 ++-
doc/src/sgml/ref/psql-ref.sgml | 4 +-
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 332 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 490 ++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 22 +-
src/backend/nodes/equalfuncs.c | 21 +-
src/backend/parser/gram.y | 307 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 144 ++++-
src/bin/pg_dump/pg_dump.h | 14 +
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/bin/psql/describe.c | 198 +++++--
src/bin/psql/tab-complete.c | 40 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 11 +-
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 33 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 522 +++++++++++++++++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 281 +++++++++-
src/tools/pgindent/typedefs.list | 5 +
45 files changed, 2836 insertions(+), 212 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..00b648a433 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6176,6 +6181,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-publication-rel">
<title><structname>pg_publication_rel</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 88646bc859..45b2e1e28f 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -108,9 +108,9 @@
<para>
Publications are different from schemas and do not affect how the table is
accessed. Each table can be added to multiple publications if needed.
- Publications may currently only contain tables. Objects must be added
- explicitly, except when a publication is created for <literal>ALL
- TABLES</literal>.
+ Publications may currently only contain tables and all tables in schema.
+ Objects must be added explicitly, except when a publication is created for
+ <literal>ALL TABLES</literal>.
</para>
<para>
@@ -534,7 +534,8 @@
and <literal>TRIGGER</literal> privilege on such tables to roles that
superusers trust. Moreover, if untrusted users can create tables, use only
publications that list tables explicitly. That is to say, create a
- subscription <literal>FOR ALL TABLES</literal> only when superusers trust
+ subscription <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> only when superusers trust
every user permitted to create a non-temp table on the publisher or the
subscriber.
</para>
@@ -564,8 +565,9 @@
<para>
To add tables to a publication, the user must have ownership rights on the
- table. To create a publication that publishes all tables automatically,
- the user must be a superuser.
+ table. To add all tables in schema to a publication, the user must be a
+ superuser. To create a publication that publishes all tables or all tables in
+ schema automatically, the user must be a superuser.
</para>
<para>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..0ea12255d3 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> and <literal>DROP</literal> clauses will add and
+ remove one or more tables/schemas from the publication. Note that adding
+ tables/schemas to a publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +69,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> or <literal>FOR ALL TABLES IN
+ SCHEMA</literal> publication must be a superuser. However, a superuser can
+ change the ownership of a publication regardless of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of the specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +114,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +168,25 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add schemas <structname>marketing</structname> and
+ <structname>sales</structname> to the publication
+ <structname>sales_publication</structname>:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing, sales;
+</programlisting>
+ </para>
+
+ <para>
+ Add tables <structname>users</structname>,
+ <structname>departments</structname> and schema
+ <structname>production</structname> to the publication
+ <structname>production_publication</structname>:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..756621cf91 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication_object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,40 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+
+ <para>
+ When a partitioned table is published via schema level publication, all
+ of its existing and future partitions irrespective of it being from the
+ publication schema or not are implicitly considered to be part of the
+ publication. So, even operations that are performed directly on a
+ partition are also published via publications that its ancestors are
+ part of. Similarly, if a partition is published via schema level
+ publication and publish_via_partition_root=true, the parent partitioned
+ table is implicitly considered to be part of the publication,
+ irrespective of it being from the publication schema or not.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +197,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +216,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +268,25 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables
+ <structname>users</structname>, <structname>departments</structname> and
+ all changes for all the tables present in the schema
+ <structname>production</structname>:
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas <structname>marketing</structname> and
+ <structname>sales</structname>:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 14e0a4dbe3..48248f750e 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1853,8 +1853,8 @@ testdb=>
If <replaceable class="parameter">pattern</replaceable> is
specified, only those publications whose names match the pattern are
listed.
- If <literal>+</literal> is appended to the command name, the tables
- associated with each publication are shown as well.
+ If <literal>+</literal> is appended to the command name, the tables and
+ schemas associated with each publication are shown as well.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..b21c1d2cc5 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -76,6 +78,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -105,6 +131,53 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
relid >= FirstNormalObjectId;
}
+/*
+ * Filter out the partitions whose parent tables were also specified in
+ * the publication.
+ */
+static List *
+filter_partitions(List *relids, List *schemarelids)
+{
+ List *result = NIL;
+ ListCell *lc;
+ ListCell *lc2;
+
+ foreach(lc, relids)
+ {
+ bool skip = false;
+ List *ancestors = NIL;
+ Oid relid = lfirst_oid(lc);
+
+ if (get_rel_relispartition(relid))
+ ancestors = get_partition_ancestors(relid);
+
+ foreach(lc2, ancestors)
+ {
+ Oid ancestor = lfirst_oid(lc2);
+
+ /*
+ * Check if the parent table exists in the published table list.
+ *
+ * XXX As of now, we do this if the partition relation or the
+ * partition relation's ancestor is present in schema publication
+ * relations.
+ */
+ if (list_member_oid(relids, ancestor) &&
+ (list_member_oid(schemarelids, relid) ||
+ list_member_oid(schemarelids, ancestor)))
+ {
+ skip = true;
+ break;
+ }
+ }
+
+ if (!skip)
+ result = lappend_oid(result, relid);
+ }
+
+ return result;
+}
+
/*
* Another variant of this, taking a Relation.
*/
@@ -262,6 +335,89 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +584,151 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[1];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the specified schema */
+ scan = table_beginscan_catalog(classRel, 1, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+ char relkind;
+
+ if (!is_publishable_class(relid, relForm))
+ continue;
+
+ relkind = get_rel_relkind(relid);
+ if (relkind == RELKIND_RELATION)
+ result = lappend_oid(result, relid);
+ else if (relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *partitionrels = NIL;
+
+ /*
+ * It is quite possible that some of the partitions are in a
+ * different schema than the parent table, so we need to get such
+ * partitions separately.
+ */
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(pubid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +856,41 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+ tables = list_concat_unique_oid(relids, schemarelids);
+ if (schemarelids && publication->pubviaroot)
+ {
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude
+ * partitions in favor of including the root partitioned
+ * tables. Otherwise, the function could return both the child
+ * and parent tables which could cause data of the child table
+ * to be double-published on the subscriber side.
+ *
+ * XXX As of now, we do this when a publication has associated
+ * schema or for all tables publication. See
+ * GetAllTablesPublicationRelations().
+ */
+ tables = filter_partitions(tables, schemarelids);
+ }
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..37dbc46a61 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,6 +36,7 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -45,11 +48,16 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
+static void LockSchemaList(List *schemalist);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,97 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and
+ * PublicationTable list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->pubtable);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
+ Relation rel = pub_rel->relation;
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +251,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +322,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (list_length(schemaidlist) > 0 && !superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +442,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +491,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +529,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -440,11 +581,111 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schemas in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +715,41 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ /*
+ * It is possible that by the time we acquire the lock on publication,
+ * concurrent DDL has removed it. We can test this by checking the
+ * existence of publication.
+ */
+ if (!SearchSysCacheExists1(PUBLICATIONOID,
+ ObjectIdGetDatum(pubform->oid)))
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication \"%s\" does not exist",
+ stmt->pubname));
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -551,10 +826,72 @@ RemovePublicationById(Oid pubid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ PublicationRelInfo *pub_rel;
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ pub_rel = palloc(sizeof(PublicationRelInfo));
+ pub_rel->relation = rel;
+ rels = lappend(rels, pub_rel);
+ }
+
+ return rels;
+}
+
/*
* Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -658,6 +995,35 @@ CloseTableList(List *rels)
}
}
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+
+ /*
+ * It is possible that by the time we acquire the lock on schema,
+ * concurrent DDL has removed it. We can test this by checking the
+ * existence of schema.
+ */
+ if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(schemaid)))
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("schema with OID %u does not exist", schemaid));
+ }
+}
+
/*
* Add listed tables to the publication.
*/
@@ -692,6 +1058,34 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
/*
* Remove listed tables from the publication.
*/
@@ -727,6 +1121,40 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 308e0adb55..53c18628a7 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1a2f159f24..857cc5ce6e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12286,6 +12286,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15994,6 +15995,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..82464c9889 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,6 +4810,19 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
+{
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
+
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(pubtable);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static PublicationTable *
_copyPublicationTable(const PublicationTable *from)
{
@@ -4827,7 +4840,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4853,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,6 +5900,9 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
+ break;
case T_PublicationTable:
retval = _copyPublicationTable(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..f537d3eb96 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2296,6 +2296,18 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(pubtable);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
static bool
_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
{
@@ -2310,7 +2322,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2334,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3894,6 +3906,9 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
+ break;
case T_PublicationTable:
retval = _equalPublicationTable(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..f5f91a3e9d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,131 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->name = $1;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = $1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * pub_obj is one of:
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9734,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12502,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15183,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17060,6 +17118,43 @@ TableFuncTypeName(List *columns)
return result;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/*
* Convert a list of (dotted) names to a RangeVar (like
* makeRangeVarFromNameList, but with position support). The
@@ -17210,6 +17305,74 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or pubtable must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubtable)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to PublicationTable */
+ PublicationTable *pubtable = makeNode(PublicationTable);
+ pubtable->relation = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->pubtable = pubtable;
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and pubtable is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->pubtable)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..20a80034ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5447,6 +5447,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5462,6 +5463,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5474,6 +5478,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1f24e79665..8691efe04b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pubinfoindex = buildIndexArray(pubinfo, numPublications,
sizeof(PublicationInfo));
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout, nspinfo, numNamespaces);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ee06dc6822..6d690ee49c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ed8ed2f266..dd1a1f4868 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,94 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4293,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4302,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10187,6 +10314,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18419,6 +18550,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 29af845ece..45b8e85b84 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -737,6 +749,8 @@ extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
+extern void getPublicationNamespaces(Archive *fout, NamespaceInfo nspinfo[],
+ int numSchemas);
extern void getSubscriptions(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..28cbe5fa7d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2302,6 +2302,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2338,6 +2347,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c..c7f97476d1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3146,17 +3146,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5020,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5052,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6209,6 +6297,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6286,15 +6412,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6327,6 +6448,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6336,31 +6458,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6373,6 +6486,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..8e01f54500 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,22 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2700,31 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..f816d14b4d 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -111,13 +111,22 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
extern bool is_publishable_relation(Relation rel);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
+extern ObjectAddress publication_add_relation(Oid pubid,
+ PublicationRelInfo *targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e0057daa06..c9b55a66f3 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,6 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
+ T_PublicationObjSpec,
T_PublicationTable,
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..49123e28a4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1816,6 +1816,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3642,12 +3643,32 @@ typedef struct PublicationTable
RangeVar *relation; /* relation to be published */
} PublicationTable;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ PublicationTable *pubtable;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3680,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c3b2b37067..24d1c7cd28 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4557,3 +4557,17 @@ DETAIL: Failing row contains (2, 1).
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..0f4fe4db8f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,23 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to create FOR ALL TABLES IN SCHEMA publication
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +390,452 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
+-- Test the list of partitions published with or without
+-- 'PUBLISH_VIA_PARTITION_ROOT' parameter
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+-----------
+ pub | sch1 | tbl1
+(1 row)
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 4ddbd16a4e..5fac2585d9 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2990,3 +2990,15 @@ insert into attach_parted_part1 values (2, 1);
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..85a5302a74 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,12 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +201,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +227,251 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
+
+-- Test the list of partitions published with or without
+-- 'PUBLISH_VIA_PARTITION_ROOT' parameter
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb5b5ec74c..a393492feb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2046,8 +2048,11 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PublicationTable
PullFilter
PullFilterOps
--
2.30.2
v46-0002-Add-tap-tests-for-the-schema-publication-feature.patchtext/x-patch; charset=US-ASCII; name=v46-0002-Add-tap-tests-for-the-schema-publication-feature.patchDownload
From 41c0307b70071bdb6cbc743927ca060ec26cb31c Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 21 Oct 2021 14:25:24 +0530
Subject: [PATCH v46 2/3] Add tap tests for the schema publication feature of
logical replication
Add tap tests for the schema publication feature of logical replication
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy, Mark Dilger
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
.../t/025_rep_changes_for_schema.pl | 168 ++++++++++++++++++
1 file changed, 168 insertions(+)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..6a3101738f
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgresNode->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgresNode->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v46-0003-Add-new-pg_publication_objects-view-to-display-T.patchtext/x-patch; charset=US-ASCII; name=v46-0003-Add-new-pg_publication_objects-view-to-display-T.patchDownload
From 7c3f0bf7b790ab77249dcae777bfd06241da06fc Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v46 3/3] Add new "pg_publication_objects" view to display
"TABLE"/"SCHEMA" publication objects
A new "pg_publication_objects" view is added, to display table/schema object
information associated with publications.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy, Mark Dilger
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 19 ++++++++
src/test/regress/expected/rules.out | 15 ++++++
3 files changed, 104 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 00b648a433..1dfb9d1a33 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..f70348e34f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,25 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..8796f71de2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,21 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM ((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Fri, Oct 22, 2021 at 11:59 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Oct 22, 2021 at 12:41 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
I was also previously concerned about what the behavior should be when
only including just the partitions of a partitioned table in a
publication using ALL TABLES IN SCHEMA and having
publish_via_partition_root=true. It seems to implicitly include the
partitioned table in the publication. But I did some testing and found
that this is the current behavior when only the partitions are
individually included in a publication using TABLE, so it seems to be
OK.Thinking some more about this, it still may still be confusing to the
user if not explicitly stated in the ALL TABLES IN SCHEMA case.
How about adding some additional explanation to the end of the
following paragraph:+ <para> + When a partitioned table is published via schema level publication, all + of its existing and future partitions irrespective of it being from the + publication schema or not are implicitly considered to be part of the + publication. So, even operations that are performed directly on a + partition are also published via publications that its ancestors are + part of. + </para>Something like:
Similarly, if a partition is published via schema level publication
and publish_via_partition_root=true, the parent partitioned table is
implicitly considered to be part of the publication, irrespective of
it being from the publication schema or not.
I have made this change in the v46 patch attached at [1]/messages/by-id/CALDaNm3kBrMO5EyEgK_TyOrBuw+RvdJ2mJfpWb5e8FbuKg2cQw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm3kBrMO5EyEgK_TyOrBuw+RvdJ2mJfpWb5e8FbuKg2cQw@mail.gmail.com
Regards,
Vignesh
On Fri, Oct 22, 2021 at 1:03 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Fri, Oct 22, 2021 at 2:25 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Oct 21, 2021 at 6:47 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for the comments, the attached v45 patch has the fix for the same.
The first patch is mostly looking good to me apart from the below
minor comments:Let me share other minor comments on v45-0001 patch:
1. + <para> + The catalog <structname>pg_publication_namespace</structname> contains the + mapping between schemas and publications in the database. This is a + many-to-many mapping.There are extra spaces after mapping at the end which are not required.
Modified
+ <literal>ADD</literal> and <literal>DROP</literal> clauses will add and + remove one or more tables/schemas from the publication. Note that adding + tables/schemas to a publication that is already subscribed to will require aThere is also an extra space after "adding".
Modified
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...] - | FOR ALL TABLES ] + [ FOR ALL TABLES + | FOR <replaceable class="parameter">publication_object</replaceable> [, ... ] ]Similarly, after "TABLES".
Modified
+ + <para> + Specifying a table that is part of a schema specified by + <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported. + </para>And, after "by".
Modified
---
+static void +AlterPublicationSchemas(AlterPublicationStmt *stmt, + HeapTuple tup, List *schemaidlist) +{ (snip) + PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt); + } + + return; +}The "return" at the end of the function is not necessary.
Modified
--- + if (pubobj->name) + pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA; + else if (!pubobj->name && !pubobj->pubtable) + pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA; + else if (!pubobj->name) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid schema name at or near"), + parser_errposition(pubobj->location));I think it's better to change the last "else if" to just "else".
Modified
--- + + if (schemarelids) + { + /* + * If the publication publishes partition changes via their + * respective root partitioned tables, we must exclude + * partitions in favor of including the root partitioned + * tables. Otherwise, the function could return both the child + * and parent tables which could cause data of the child table + * to be double-published on the subscriber side. + * + * XXX As of now, we do this when a publication has associated + * schema or for all tables publication. See + * GetAllTablesPublicationRelations(). + */ + tables = list_concat_unique_oid(relids, schemarelids); + if (publication->pubviaroot) + tables = filter_partitions(tables, schemarelids); + } + else + tables = relids; + + }There is an extra newline after "table = relids;".
Removed it
I have made this change in the v46 patch attached at [1]/messages/by-id/CALDaNm3kBrMO5EyEgK_TyOrBuw+RvdJ2mJfpWb5e8FbuKg2cQw@mail.gmail.com.
[1]: /messages/by-id/CALDaNm3kBrMO5EyEgK_TyOrBuw+RvdJ2mJfpWb5e8FbuKg2cQw@mail.gmail.com
Regards,
Vignesh
On Fri, Oct 22, 2021 at 11:59 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Fri, Oct 22, 2021 at 12:41 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
I was also previously concerned about what the behavior should be when
only including just the partitions of a partitioned table in a
publication using ALL TABLES IN SCHEMA and having
publish_via_partition_root=true. It seems to implicitly include the
partitioned table in the publication. But I did some testing and found
that this is the current behavior when only the partitions are
individually included in a publication using TABLE, so it seems to be
OK.Thinking some more about this, it still may still be confusing to the
user if not explicitly stated in the ALL TABLES IN SCHEMA case.
How about adding some additional explanation to the end of the
following paragraph:+ <para> + When a partitioned table is published via schema level publication, all + of its existing and future partitions irrespective of it being from the + publication schema or not are implicitly considered to be part of the + publication. So, even operations that are performed directly on a + partition are also published via publications that its ancestors are + part of. + </para>Something like:
Similarly, if a partition is published via schema level publication
and publish_via_partition_root=true, the parent partitioned table is
implicitly considered to be part of the publication, irrespective of
it being from the publication schema or not.
I don't think we need to explicitly mention this in docs as this is
quite similar to what is happening for the "For Table" case as well
and it seems to be clarified by "publish_via_partition_root"
definition in Create Publication docs [1]https://www.postgresql.org/docs/devel/sql-createpublication.html.
[1]: https://www.postgresql.org/docs/devel/sql-createpublication.html
--
With Regards,
Amit Kapila.
On Fri, Oct 22, 2021 at 8:56 PM vignesh C <vignesh21@gmail.com> wrote:
I am getting a compilation error in the latest patch on HEAD. I think
was relying on some variable removed by a recent commit
92316a4582a5714d4e494aaf90360860e7fec37a. While looking at that
compilation error, I observed that we don't need the second and third
parameters in pg_dump.c/getPublicationNamespaces() as those are not
getting used.
--
With Regards,
Amit Kapila.
On Mon, Oct 25, 2021 at 10:52 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Oct 22, 2021 at 8:56 PM vignesh C <vignesh21@gmail.com> wrote:
I am getting a compilation error in the latest patch on HEAD. I think
was relying on some variable removed by a recent commit
92316a4582a5714d4e494aaf90360860e7fec37a. While looking at that
compilation error, I observed that we don't need the second and third
parameters in pg_dump.c/getPublicationNamespaces() as those are not
getting used.
I have fixed this in the v47 version attached.
Regards,
Vignesh
Attachments:
v47-0001-Allow-publishing-the-tables-of-schema.patchtext/x-patch; charset=US-ASCII; name=v47-0001-Allow-publishing-the-tables-of-schema.patchDownload
From 61edb8f41a201bed532af6aafa7c0b9f3425d229 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Mon, 25 Oct 2021 11:10:25 +0530
Subject: [PATCH v47 1/3] Allow publishing the tables of schema.
A new option "FOR ALL TABLES IN SCHEMA" in Create/Alter Publication allows
one or more schemas to be specified, whose tables are selected by the
publisher for sending the data to the subscriber.
The new syntax allows specifying both the tables and schemas. For example:
CREATE PUBLICATION pub1 FOR TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2;
OR
ALTER PUBLICATION pub1 ADD TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2;
A new system table "pg_publication_namespace" has been added, to maintain
the schemas that the user wants to publish through the publication.
Modified the output plugin (pgoutput) to publish the changes if the
relation is part of schema publication.
Updates pg_dump to identify and dump schema publications. Updates the \d
family of commands to display schema publications and \dRp+ variant will
now display associated schemas if any.
CATALOG_VERSION_NO needs to be updated while committing, as this feature involves a catalog change.
Author: Vignesh C, Hou Zhijie, Amit Kapila
Syntax-by: Tom Lane, Alvaro Herrera, Peter Eisentraut
Reviewed-by: Greg Nancarrow, Masahiko Sawada, Hou Zhijie, Amit Kapila, Haiying Tang, Ajin Cherian, Rahila Syed, Bharath Rupireddy, Mark Dilger
Tested-by: Haiying Tang
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ@mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 72 ++-
doc/src/sgml/logical-replication.sgml | 14 +-
doc/src/sgml/ref/alter_publication.sgml | 77 ++-
doc/src/sgml/ref/create_publication.sgml | 76 ++-
doc/src/sgml/ref/psql-ref.sgml | 4 +-
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 9 +
src/backend/catalog/objectaddress.c | 149 +++++
src/backend/catalog/pg_publication.c | 332 ++++++++++-
src/backend/commands/alter.c | 1 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/publicationcmds.c | 490 ++++++++++++++--
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 28 +
src/backend/nodes/copyfuncs.c | 22 +-
src/backend/nodes/equalfuncs.c | 21 +-
src/backend/parser/gram.y | 307 +++++++---
src/backend/replication/pgoutput/pgoutput.c | 19 +-
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 23 +
src/bin/pg_dump/common.c | 5 +-
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 143 ++++-
src/bin/pg_dump/pg_dump.h | 13 +
src/bin/pg_dump/pg_dump_sort.c | 7 +
src/bin/pg_dump/t/002_pg_dump.pl | 30 +
src/bin/psql/describe.c | 198 +++++--
src/bin/psql/tab-complete.c | 40 +-
src/include/catalog/dependency.h | 1 +
src/include/catalog/pg_publication.h | 8 +
.../catalog/pg_publication_namespace.h | 47 ++
src/include/commands/publicationcmds.h | 1 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 33 +-
src/include/utils/syscache.h | 2 +
src/test/regress/expected/alter_table.out | 14 +
src/test/regress/expected/object_address.out | 6 +-
src/test/regress/expected/oidjoins.out | 2 +
src/test/regress/expected/publication.out | 522 +++++++++++++++++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 12 +
src/test/regress/sql/object_address.sql | 3 +
src/test/regress/sql/publication.sql | 281 +++++++++-
src/tools/pgindent/typedefs.list | 5 +
45 files changed, 2829 insertions(+), 211 deletions(-)
create mode 100644 src/include/catalog/pg_publication_namespace.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..00b648a433 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,11 @@
<entry>publications for logical replication</entry>
</row>
+ <row>
+ <entry><link linkend="catalog-pg-publication-namespace"><structname>pg_publication_namespace</structname></link></entry>
+ <entry>schema to publication mapping</entry>
+ </row>
+
<row>
<entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
<entry>relation to publication mapping</entry>
@@ -6176,6 +6181,67 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-publication-namespace">
+ <title><structname>pg_publication_namespace</structname></title>
+
+ <indexterm zone="catalog-pg-publication-namespace">
+ <primary>pg_publication_namespace</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_publication_namespace</structname> contains the
+ mapping between schemas and publications in the database. This is a
+ many-to-many mapping.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_namespace</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnpubid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pnnspid</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Reference to schema
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-publication-rel">
<title><structname>pg_publication_rel</structname></title>
@@ -11278,9 +11344,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
information about the mapping between publications and the tables they
contain. Unlike the underlying catalog
<link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
- this view expands
- publications defined as <literal>FOR ALL TABLES</literal>, so for such
- publications there will be a row for each eligible table.
+ this view expands publications defined as <literal>FOR ALL TABLES</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible table.
</para>
<table>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 88646bc859..45b2e1e28f 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -108,9 +108,9 @@
<para>
Publications are different from schemas and do not affect how the table is
accessed. Each table can be added to multiple publications if needed.
- Publications may currently only contain tables. Objects must be added
- explicitly, except when a publication is created for <literal>ALL
- TABLES</literal>.
+ Publications may currently only contain tables and all tables in schema.
+ Objects must be added explicitly, except when a publication is created for
+ <literal>ALL TABLES</literal>.
</para>
<para>
@@ -534,7 +534,8 @@
and <literal>TRIGGER</literal> privilege on such tables to roles that
superusers trust. Moreover, if untrusted users can create tables, use only
publications that list tables explicitly. That is to say, create a
- subscription <literal>FOR ALL TABLES</literal> only when superusers trust
+ subscription <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> only when superusers trust
every user permitted to create a non-temp table on the publisher or the
subscriber.
</para>
@@ -564,8 +565,9 @@
<para>
To add tables to a publication, the user must have ownership rights on the
- table. To create a publication that publishes all tables automatically,
- the user must be a superuser.
+ table. To add all tables in schema to a publication, the user must be a
+ superuser. To create a publication that publishes all tables or all tables in
+ schema automatically, the user must be a superuser.
</para>
<para>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index faa114b2c6..0ea12255d3 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -21,12 +21,17 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
-ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> ADD <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET <replaceable class="parameter">publication_object</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="parameter">name</replaceable> DROP <replaceable class="parameter">publication_object</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -39,14 +44,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</para>
<para>
- The first three variants change which tables are part of the publication.
- The <literal>SET TABLE</literal> clause will replace the list of tables in
- the publication with the specified one. The <literal>ADD TABLE</literal>
- and <literal>DROP TABLE</literal> clauses will add and remove one or more
- tables from the publication. Note that adding tables to a publication that
- is already subscribed to will require a <literal>ALTER SUBSCRIPTION
- ... REFRESH PUBLICATION</literal> action on the subscribing side in order
- to become effective.
+ The first three variants change which tables/schemas are part of the
+ publication. The <literal>SET</literal> clause will replace the list of
+ tables/schemas in the publication with the specified list; the existing
+ tables/schemas that were present in the publication will be removed. The
+ <literal>ADD</literal> and <literal>DROP</literal> clauses will add and
+ remove one or more tables/schemas from the publication. Note that adding
+ tables/schemas to a publication that is already subscribed to will require a
+ <literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
+ subscribing side in order to become effective.
</para>
<para>
@@ -63,11 +69,22 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
You must own the publication to use <command>ALTER PUBLICATION</command>.
Adding a table to a publication additionally requires owning that table.
- To alter the owner, you must also be a direct or indirect member of the new
- owning role. The new owner must have <literal>CREATE</literal> privilege on
- the database. Also, the new owner of a <literal>FOR ALL TABLES</literal>
- publication must be a superuser. However, a superuser can change the
- ownership of a publication regardless of these restrictions.
+ The <literal>ADD ALL TABLES IN SCHEMA</literal> and
+ <literal>SET ALL TABLES IN SCHEMA</literal> to a publication requires the
+ invoking user to be a superuser. To alter the owner, you must also be a
+ direct or indirect member of the new owning role. The new owner must have
+ <literal>CREATE</literal> privilege on the database. Also, the new owner
+ of a <literal>FOR ALL TABLES</literal> or <literal>FOR ALL TABLES IN
+ SCHEMA</literal> publication must be a superuser. However, a superuser can
+ change the ownership of a publication regardless of these restrictions.
+ </para>
+
+ <para>
+ Adding/Setting a table that is part of schema specified in
+ <literal>ALL TABLES IN SCHEMA</literal>, adding/setting a schema to a
+ publication that already has a table that is part of specified schema or
+ adding/setting a table to a publication that already has a table's schema as
+ part of the specified schema is not supported.
</para>
</refsect1>
@@ -97,6 +114,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">schema_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -142,6 +168,25 @@ ALTER PUBLICATION noinsert SET (publish = 'update, delete');
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</programlisting></para>
+
+ <para>
+ Add schemas <structname>marketing</structname> and
+ <structname>sales</structname> to the publication
+ <structname>sales_publication</structname>:
+<programlisting>
+ALTER PUBLICATION sales_publication ADD ALL TABLES IN SCHEMA marketing, sales;
+</programlisting>
+ </para>
+
+ <para>
+ Add tables <structname>users</structname>,
+ <structname>departments</structname> and schema
+ <structname>production</structname> to the publication
+ <structname>production_publication</structname>:
+<programlisting>
+ALTER PUBLICATION production_publication ADD TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index ff82fbca55..ca01d8c29b 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -22,9 +22,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
- [ FOR TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ...]
- | FOR ALL TABLES ]
+ [ FOR ALL TABLES
+ | FOR <replaceable class="parameter">publication_object</replaceable> [, ... ] ]
[ WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
+
+<phrase>where <replaceable class="parameter">publication_object</replaceable> is one of:</phrase>
+
+ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [, ... ]
+ ALL TABLES IN SCHEMA { <replaceable class="parameter">schema_name</replaceable> | CURRENT_SCHEMA } [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -86,6 +91,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
partition are also published via publications that its ancestors are
part of.
</para>
+
+ <para>
+ Specifying a table that is part of a schema specified by
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not supported.
+ </para>
</listitem>
</varlistentry>
@@ -99,6 +109,37 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>FOR ALL TABLES IN SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Marks the publication as one that replicates changes for all tables in
+ the specified list of schemas, including tables created in the future.
+ </para>
+
+ <para>
+ Specifying a schema along with a table which belongs to the specified
+ schema using <literal>FOR TABLE</literal> is not supported.
+ </para>
+
+ <para>
+ Only persistent base tables and partitioned tables present in the schema
+ will be included as part of the publication. Temporary tables, unlogged
+ tables, foreign tables, materialized views, and regular views from the
+ schema will not be part of the publication.
+ </para>
+
+ <para>
+ When a partitioned table is published via schema level publication, all
+ of its existing and future partitions irrespective of it being from the
+ publication schema or not are implicitly considered to be part of the
+ publication. So, even operations that are performed directly on a
+ partition are also published via publications that its ancestors are
+ part of.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>WITH ( <replaceable class="parameter">publication_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>
@@ -153,9 +194,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<title>Notes</title>
<para>
- If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
- TABLES</literal> is specified, then the publication starts out with an
- empty set of tables. That is useful if tables are to be added later.
+ If <literal>FOR TABLE</literal>, <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL TABLES IN SCHEMA</literal> is not specified, then the
+ publication starts out with an empty set of tables. That is useful if
+ tables or schemas are to be added later.
</para>
<para>
@@ -171,8 +213,9 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
To add a table to a publication, the invoking user must have ownership
- rights on the table. The <command>FOR ALL TABLES</command> clause requires
- the invoking user to be a superuser.
+ rights on the table. The <command>FOR ALL TABLES</command> and
+ <command>FOR ALL TABLES IN SCHEMA</command> clauses require the invoking
+ user to be a superuser.
</para>
<para>
@@ -222,6 +265,25 @@ CREATE PUBLICATION alltables FOR ALL TABLES;
<programlisting>
CREATE PUBLICATION insert_only FOR TABLE mydata
WITH (publish = 'insert');
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for tables
+ <structname>users</structname>, <structname>departments</structname> and
+ all changes for all the tables present in the schema
+ <structname>production</structname>:
+<programlisting>
+CREATE PUBLICATION production_publication FOR TABLE users, departments, ALL TABLES IN SCHEMA production;
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all changes for all the tables present in
+ the schemas <structname>marketing</structname> and
+ <structname>sales</structname>:
+<programlisting>
+CREATE PUBLICATION sales_publication FOR ALL TABLES IN SCHEMA marketing, sales;
</programlisting></para>
</refsect1>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 14e0a4dbe3..48248f750e 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1853,8 +1853,8 @@ testdb=>
If <replaceable class="parameter">pattern</replaceable> is
specified, only those publications whose names match the pattern are
listed.
- If <literal>+</literal> is appended to the command name, the tables
- associated with each publication are shown as well.
+ If <literal>+</literal> is appended to the command name, the tables and
+ schemas associated with each publication are shown as well.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..4e6efda97f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -68,8 +68,8 @@ CATALOG_HEADERS := \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
- pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h
+ pg_sequence.h pg_publication.h pg_publication_namespace.h \
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 89792b154e..ce0a4ff14e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3427,6 +3427,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3566,6 +3567,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 91c3e976e0..9f8eb1a37f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -49,6 +49,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
+ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
@@ -1456,6 +1458,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ RemovePublicationSchemaById(object->objectId);
+ break;
+
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
@@ -2850,6 +2856,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+ case PublicationNamespaceRelationId:
+ return OCLASS_PUBLICATION_NAMESPACE;
+
case PublicationRelationId:
return OCLASS_PUBLICATION;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8c94939baa..2bae3fbb17 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
@@ -825,6 +826,10 @@ static const struct object_type_map
{
"publication", OBJECT_PUBLICATION
},
+ /* OCLASS_PUBLICATION_NAMESPACE */
+ {
+ "publication namespace", OBJECT_PUBLICATION_NAMESPACE
+ },
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
@@ -875,6 +880,8 @@ static ObjectAddress get_object_address_usermapping(List *object,
static ObjectAddress get_object_address_publication_rel(List *object,
Relation *relp,
bool missing_ok);
+static ObjectAddress get_object_address_publication_schema(List *object,
+ bool missing_ok);
static ObjectAddress get_object_address_defacl(List *object,
bool missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -1113,6 +1120,10 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_usermapping(castNode(List, object),
missing_ok);
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
+ address = get_object_address_publication_schema(castNode(List, object),
+ missing_ok);
+ break;
case OBJECT_PUBLICATION_REL:
address = get_object_address_publication_rel(castNode(List, object),
&relation,
@@ -1935,6 +1946,49 @@ get_object_address_publication_rel(List *object,
return address;
}
+/*
+ * Find the ObjectAddress for a publication schema. The first element of the
+ * object parameter is the schema name, the second is the publication name.
+ */
+static ObjectAddress
+get_object_address_publication_schema(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ Publication *pub;
+ char *pubname;
+ char *schemaname;
+ Oid schemaid;
+
+ ObjectAddressSet(address, PublicationNamespaceRelationId, InvalidOid);
+
+ /* Fetch schema name and publication name from input list */
+ schemaname = strVal(linitial(object));
+ pubname = strVal(lsecond(object));
+
+ schemaid = get_namespace_oid(schemaname, missing_ok);
+ if (!OidIsValid(schemaid))
+ return address;
+
+ /* Now look up the pg_publication tuple */
+ pub = GetPublicationByName(pubname, missing_ok);
+ if (!pub)
+ return address;
+
+ /* Find the publication schema mapping in syscache */
+ address.objectId =
+ GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pub->oid));
+ if (!OidIsValid(address.objectId) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
+ schemaname, pubname)));
+
+ return address;
+}
+
/*
* Find the ObjectAddress for a default ACL.
*/
@@ -2206,6 +2260,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_DOMCONSTRAINT:
case OBJECT_CAST:
case OBJECT_USER_MAPPING:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_DEFACL:
case OBJECT_TRANSFORM:
@@ -2299,6 +2354,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_PUBLICATION_REL:
objnode = (Node *) list_make2(name, linitial(args));
break;
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_USER_MAPPING:
objnode = (Node *) list_make2(linitial(name), linitial(args));
break;
@@ -2848,6 +2904,55 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
return tuple;
}
+/*
+ * getPublicationSchemaInfo
+ *
+ * Get publication name and schema name from the object address into pubname and
+ * nspname. Both pubname and nspname are palloc'd strings which will be freed by
+ * the caller.
+ */
+static bool
+getPublicationSchemaInfo(const ObjectAddress *object, bool missing_ok,
+ char **pubname, char **nspname)
+{
+ HeapTuple tup;
+ Form_pg_publication_namespace pnform;
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object->objectId);
+ return false;
+ }
+
+ pnform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+ *pubname = get_publication_name(pnform->pnpubid, missing_ok);
+ if (!(*pubname))
+ {
+ ReleaseSysCache(tup);
+ return false;
+ }
+
+ *nspname = get_namespace_name(pnform->pnnspid);
+ if (!(*nspname))
+ {
+ Oid schemaid = pnform->pnnspid;
+
+ pfree(*pubname);
+ ReleaseSysCache(tup);
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for schema %u",
+ schemaid);
+ return false;
+ }
+
+ ReleaseSysCache(tup);
+ return true;
+}
+
/*
* getObjectDescription: build an object description for messages
*
@@ -3872,6 +3977,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok,
+ &pubname, &nspname))
+ break;
+
+ appendStringInfo(&buffer, _("publication of schema %s in publication %s"),
+ nspname, pubname);
+ pfree(pubname);
+ pfree(nspname);
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
@@ -4473,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "publication");
break;
+ case OCLASS_PUBLICATION_NAMESPACE:
+ appendStringInfoString(&buffer, "publication namespace");
+ break;
+
case OCLASS_PUBLICATION_REL:
appendStringInfoString(&buffer, "publication relation");
break;
@@ -5683,6 +5808,30 @@ getObjectIdentityParts(const ObjectAddress *object,
break;
}
+ case OCLASS_PUBLICATION_NAMESPACE:
+ {
+ char *pubname;
+ char *nspname;
+
+ if (!getPublicationSchemaInfo(object, missing_ok, &pubname,
+ &nspname))
+ break;
+ appendStringInfo(&buffer, "%s in publication %s",
+ nspname, pubname);
+
+ if (objargs)
+ *objargs = list_make1(pubname);
+ else
+ pfree(pubname);
+
+ if (objname)
+ *objname = list_make1(nspname);
+ else
+ pfree(nspname);
+
+ break;
+ }
+
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 9cd0c82f93..b21c1d2cc5 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -28,7 +28,9 @@
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/publicationcmds.h"
@@ -76,6 +78,30 @@ check_publication_add_relation(Relation targetrel)
errdetail("Temporary and unlogged relations cannot be replicated.")));
}
+/*
+ * Check if schema can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_schema(Oid schemaid)
+{
+ /* Can't be system namespace */
+ if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("This operation is not supported for system schemas.")));
+
+ /* Can't be temporary namespace */
+ if (isAnyTempNamespace(schemaid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(schemaid)),
+ errdetail("Temporary schemas cannot be replicated.")));
+}
+
/*
* Returns if relation represented by oid and Form_pg_class entry
* is publishable.
@@ -105,6 +131,53 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
relid >= FirstNormalObjectId;
}
+/*
+ * Filter out the partitions whose parent tables were also specified in
+ * the publication.
+ */
+static List *
+filter_partitions(List *relids, List *schemarelids)
+{
+ List *result = NIL;
+ ListCell *lc;
+ ListCell *lc2;
+
+ foreach(lc, relids)
+ {
+ bool skip = false;
+ List *ancestors = NIL;
+ Oid relid = lfirst_oid(lc);
+
+ if (get_rel_relispartition(relid))
+ ancestors = get_partition_ancestors(relid);
+
+ foreach(lc2, ancestors)
+ {
+ Oid ancestor = lfirst_oid(lc2);
+
+ /*
+ * Check if the parent table exists in the published table list.
+ *
+ * XXX As of now, we do this if the partition relation or the
+ * partition relation's ancestor is present in schema publication
+ * relations.
+ */
+ if (list_member_oid(relids, ancestor) &&
+ (list_member_oid(schemarelids, relid) ||
+ list_member_oid(schemarelids, ancestor)))
+ {
+ skip = true;
+ break;
+ }
+ }
+
+ if (!skip)
+ result = lappend_oid(result, relid);
+ }
+
+ return result;
+}
+
/*
* Another variant of this, taking a Relation.
*/
@@ -262,6 +335,89 @@ publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
return myself;
}
+/*
+ * Insert new publication / schema mapping.
+ */
+ObjectAddress
+publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
+{
+ Relation rel;
+ HeapTuple tup;
+ Datum values[Natts_pg_publication_namespace];
+ bool nulls[Natts_pg_publication_namespace];
+ Oid psschid;
+ Publication *pub = GetPublication(pubid);
+ List *schemaRels = NIL;
+ ObjectAddress myself,
+ referenced;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid)))
+ {
+ table_close(rel, RowExclusiveLock);
+
+ if (if_not_exists)
+ return InvalidObjectAddress;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("schema \"%s\" is already member of publication \"%s\"",
+ get_namespace_name(schemaid), pub->name)));
+ }
+
+ check_publication_add_schema(schemaid);
+
+ /* Form a tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
+ Anum_pg_publication_namespace_oid);
+ values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
+ values[Anum_pg_publication_namespace_pnpubid - 1] =
+ ObjectIdGetDatum(pubid);
+ values[Anum_pg_publication_namespace_pnnspid - 1] =
+ ObjectIdGetDatum(schemaid);
+
+ tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+ /* Insert tuple into catalog */
+ CatalogTupleInsert(rel, tup);
+ heap_freetuple(tup);
+
+ ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
+
+ /* Add dependency on the publication */
+ ObjectAddressSet(referenced, PublicationRelationId, pubid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Add dependency on the schema */
+ ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+ /* Close the table */
+ table_close(rel, RowExclusiveLock);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * publication_add_relation for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(schemaid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ return myself;
+}
+
/* Gets list of publication oids for a relation */
List *
GetRelationPublications(Oid relid)
@@ -428,6 +584,151 @@ GetAllTablesPublicationRelations(bool pubviaroot)
return result;
}
+/*
+ * Gets the list of schema oids for a publication.
+ *
+ * This should only be used FOR ALL TABLES IN SCHEMA publications.
+ */
+List *
+GetPublicationSchemas(Oid pubid)
+{
+ List *result = NIL;
+ Relation pubschsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /* Find all schemas associated with the publication */
+ pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_namespace_pnpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(pubid));
+
+ scan = systable_beginscan(pubschsrel,
+ PublicationNamespacePnnspidPnpubidIndexId,
+ true, NULL, 1, &scankey);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_namespace pubsch;
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ result = lappend_oid(result, pubsch->pnnspid);
+ }
+
+ systable_endscan(scan);
+ table_close(pubschsrel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * Gets the list of publication oids associated with a specified schema.
+ */
+List *
+GetSchemaPublications(Oid schemaid)
+{
+ List *result = NIL;
+ CatCList *pubschlist;
+ int i;
+
+ /* Find all publications associated with the schema */
+ pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
+ ObjectIdGetDatum(schemaid));
+ for (i = 0; i < pubschlist->n_members; i++)
+ {
+ HeapTuple tup = &pubschlist->members[i]->tuple;
+ Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
+
+ result = lappend_oid(result, pubid);
+ }
+
+ ReleaseSysCacheList(pubschlist);
+
+ return result;
+}
+
+/*
+ * Get the list of publishable relation oids for a specified schema.
+ */
+List *
+GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
+{
+ Relation classRel;
+ ScanKeyData key[1];
+ TableScanDesc scan;
+ HeapTuple tuple;
+ List *result = NIL;
+
+ Assert(OidIsValid(schemaid));
+
+ classRel = table_open(RelationRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ schemaid);
+
+ /* get all the relations present in the specified schema */
+ scan = table_beginscan_catalog(classRel, 1, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
+ Oid relid = relForm->oid;
+ char relkind;
+
+ if (!is_publishable_class(relid, relForm))
+ continue;
+
+ relkind = get_rel_relkind(relid);
+ if (relkind == RELKIND_RELATION)
+ result = lappend_oid(result, relid);
+ else if (relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ List *partitionrels = NIL;
+
+ /*
+ * It is quite possible that some of the partitions are in a
+ * different schema than the parent table, so we need to get such
+ * partitions separately.
+ */
+ partitionrels = GetPubPartitionOptionRelations(partitionrels,
+ pub_partopt,
+ relForm->oid);
+ result = list_concat_unique_oid(result, partitionrels);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(classRel, AccessShareLock);
+ return result;
+}
+
+/*
+ * Gets the list of all relations published by FOR ALL TABLES IN SCHEMA
+ * publication.
+ */
+List *
+GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+{
+ List *result = NIL;
+ List *pubschemalist = GetPublicationSchemas(pubid);
+ ListCell *cell;
+
+ foreach(cell, pubschemalist)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ List *schemaRels = NIL;
+
+ schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
+ result = list_concat(result, schemaRels);
+ }
+
+ return result;
+}
+
/*
* Get publication using oid
*
@@ -555,12 +856,41 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
* need those.
*/
if (publication->alltables)
+ {
tables = GetAllTablesPublicationRelations(publication->pubviaroot);
+ }
else
- tables = GetPublicationRelations(publication->oid,
+ {
+ List *relids,
+ *schemarelids;
+
+ relids = GetPublicationRelations(publication->oid,
publication->pubviaroot ?
PUBLICATION_PART_ROOT :
PUBLICATION_PART_LEAF);
+ schemarelids = GetAllSchemaPublicationRelations(publication->oid,
+ publication->pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
+ tables = list_concat_unique_oid(relids, schemarelids);
+ if (schemarelids && publication->pubviaroot)
+ {
+ /*
+ * If the publication publishes partition changes via their
+ * respective root partitioned tables, we must exclude
+ * partitions in favor of including the root partitioned
+ * tables. Otherwise, the function could return both the child
+ * and parent tables which could cause data of the child table
+ * to be double-published on the subscriber side.
+ *
+ * XXX As of now, we do this when a publication has associated
+ * schema or for all tables publication. See
+ * GetAllTablesPublicationRelations().
+ */
+ tables = filter_partitions(tables, schemarelids);
+ }
+ }
+
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c47d54e96b..40044070cf 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -660,6 +660,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_EVENT_TRIGGER:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71612d577e..df264329d8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -973,6 +973,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_POLICY:
case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROUTINE:
case OBJECT_RULE:
@@ -1050,6 +1051,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -2126,6 +2128,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -2208,6 +2211,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 9c7f91611d..d1fff13d2e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -25,7 +25,9 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -34,6 +36,7 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -45,11 +48,16 @@
#include "utils/syscache.h"
#include "utils/varlena.h"
+static List *OpenReliIdList(List *relids);
static List *OpenTableList(List *tables);
static void CloseTableList(List *rels);
+static void LockSchemaList(List *schemalist);
static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt);
+static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
@@ -135,6 +143,97 @@ parse_publication_options(ParseState *pstate,
}
}
+/*
+ * Convert the PublicationObjSpecType list into schema oid list and
+ * PublicationTable list.
+ */
+static void
+ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
+ List **rels, List **schemas)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+
+ if (!pubobjspec_list)
+ return;
+
+ foreach(cell, pubobjspec_list)
+ {
+ Oid schemaid;
+ List *search_path;
+
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ switch (pubobj->pubobjtype)
+ {
+ case PUBLICATIONOBJ_TABLE:
+ *rels = lappend(*rels, pubobj->pubtable);
+ break;
+ case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ schemaid = get_namespace_oid(pubobj->name, false);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ case PUBLICATIONOBJ_CURRSCHEMA:
+ search_path = fetch_search_path(false);
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected for CURRENT_SCHEMA"));
+
+ schemaid = linitial_oid(search_path);
+ list_free(search_path);
+
+ /* Filter out duplicates if user specifies "sch1, sch1" */
+ *schemas = list_append_unique_oid(*schemas, schemaid);
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid publication object type %d", pubobj->pubobjtype);
+ break;
+ }
+ }
+}
+
+/*
+ * Check if any of the given relation's schema is a member of the given schema
+ * list.
+ */
+static void
+CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
+ PublicationObjSpecType checkobjtype)
+{
+ ListCell *lc;
+
+ foreach(lc, rels)
+ {
+ PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
+ Relation rel = pub_rel->relation;
+ Oid relSchemaId = RelationGetNamespace(rel);
+
+ if (list_member_oid(schemaidlist, relSchemaId))
+ {
+ if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add schema \"%s\" to publication",
+ get_namespace_name(relSchemaId)),
+ errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
+ RelationGetRelationName(rel),
+ get_namespace_name(relSchemaId)));
+ else if (checkobjtype == PUBLICATIONOBJ_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add relation \"%s.%s\" to publication",
+ get_namespace_name(relSchemaId),
+ RelationGetRelationName(rel)),
+ errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
+ get_namespace_name(relSchemaId)));
+ }
+ }
+}
+
/*
* Create new publication.
*/
@@ -152,6 +251,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
bool publish_via_partition_root_given;
bool publish_via_partition_root;
AclResult aclresult;
+ List *relations = NIL;
+ List *schemaidlist = NIL;
/* must have CREATE privilege on database */
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
@@ -221,21 +322,44 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Make the changes visible. */
CommandCounterIncrement();
- if (stmt->tables)
- {
- List *rels;
-
- Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
- }
- else if (stmt->for_all_tables)
+ /* Associate objects with the publication. */
+ if (stmt->for_all_tables)
{
/* Invalidate relcache so that publication info is rebuilt. */
CacheInvalidateRelcacheAll();
}
+ else
+ {
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ /* FOR ALL TABLES IN SCHEMA requires superuser */
+ if (list_length(schemaidlist) > 0 && !superuser())
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create FOR ALL TABLES IN SCHEMA publication"));
+
+ if (list_length(relations) > 0)
+ {
+ List *rels;
+
+ rels = OpenTableList(relations);
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+ PublicationAddTables(puboid, rels, true, NULL);
+ CloseTableList(rels);
+ }
+
+ if (list_length(schemaidlist) > 0)
+ {
+ /*
+ * Schema lock is held until the publication is created to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ PublicationAddSchemas(puboid, schemaidlist, true, NULL);
+ }
+ }
table_close(rel, RowExclusiveLock);
@@ -318,13 +442,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
else
{
+ List *relids = NIL;
+ List *schemarelids = NIL;
+
/*
* For any partitioned tables contained in the publication, we must
* invalidate all partitions contained in the respective partition
* trees, not just those explicitly mentioned in the publication.
*/
- List *relids = GetPublicationRelations(pubform->oid,
- PUBLICATION_PART_ALL);
+ relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ schemarelids = GetAllSchemaPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ALL);
+ relids = list_concat_unique_oid(relids, schemarelids);
InvalidatePublicationRels(relids);
}
@@ -361,28 +491,36 @@ InvalidatePublicationRels(List *relids)
* Add or remove table to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
- HeapTuple tup)
+AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
{
List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
- /* Check that user is allowed to manipulate the publication tables. */
- if (pubform->puballtables)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("publication \"%s\" is defined as FOR ALL TABLES",
- NameStr(pubform->pubname)),
- errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * tables in which case we need to remove all the existing tables.
+ */
+ if (!tables && stmt->action != DEFELEM_SET)
+ return;
- Assert(list_length(stmt->tables) > 0);
+ rels = OpenTableList(tables);
- rels = OpenTableList(stmt->tables);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *schemas = NIL;
- if (stmt->tableAction == DEFELEM_ADD)
+ /*
+ * Check if the relation is member of the existing schema in the
+ * publication or member of the schema list specified.
+ */
+ schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
+ CheckObjSchemaNotAlreadyInPublication(rels, schemas,
+ PUBLICATIONOBJ_TABLE);
PublicationAddTables(pubid, rels, false, stmt);
- else if (stmt->tableAction == DEFELEM_DROP)
+ }
+ else if (stmt->action == DEFELEM_DROP)
PublicationDropTables(pubid, rels, false);
else /* DEFELEM_SET */
{
@@ -391,6 +529,9 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
List *delrels = NIL;
ListCell *oldlc;
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_TABLE);
+
/* Calculate which relations to drop. */
foreach(oldlc, oldrelids)
{
@@ -440,11 +581,111 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
CloseTableList(rels);
}
+/*
+ * Alter the publication schemas.
+ *
+ * Add or remove schemas to/from publication.
+ */
+static void
+AlterPublicationSchemas(AlterPublicationStmt *stmt,
+ HeapTuple tup, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ /*
+ * It is quite possible that for the SET case user has not specified any
+ * schemas in which case we need to remove all the existing schemas.
+ */
+ if (!schemaidlist && stmt->action != DEFELEM_SET)
+ return;
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(schemaidlist);
+ if (stmt->action == DEFELEM_ADD)
+ {
+ List *rels;
+ List *reloids;
+
+ reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
+ rels = OpenReliIdList(reloids);
+
+ CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
+ PUBLICATIONOBJ_REL_IN_SCHEMA);
+
+ CloseTableList(rels);
+ PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
+ }
+ else if (stmt->action == DEFELEM_DROP)
+ PublicationDropSchemas(pubform->oid, schemaidlist, false);
+ else /* DEFELEM_SET */
+ {
+ List *oldschemaids = GetPublicationSchemas(pubform->oid);
+ List *delschemas = NIL;
+
+ /* Identify which schemas should be dropped */
+ delschemas = list_difference_oid(oldschemaids, schemaidlist);
+
+ /*
+ * Schema lock is held until the publication is altered to prevent
+ * concurrent schema deletion.
+ */
+ LockSchemaList(delschemas);
+
+ /* And drop them */
+ PublicationDropSchemas(pubform->oid, delschemas, true);
+
+ /*
+ * Don't bother calculating the difference for adding, we'll catch and
+ * skip existing ones when doing catalog update.
+ */
+ PublicationAddSchemas(pubform->oid, schemaidlist, true, stmt);
+ }
+}
+
+/*
+ * Check if relations and schemas can be in a given publication and throw
+ * appropriate error if not.
+ */
+static void
+CheckAlterPublication(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *schemaidlist)
+{
+ Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+ if ((stmt->action == DEFELEM_ADD || stmt->action == DEFELEM_SET) &&
+ schemaidlist && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to add or set schemas")));
+
+ /*
+ * Check that user is allowed to manipulate the publication tables in
+ * schema
+ */
+ if (schemaidlist && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.")));
+
+ /* Check that user is allowed to manipulate the publication tables. */
+ if (tables && pubform->puballtables)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+ NameStr(pubform->pubname)),
+ errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
+}
+
/*
* Alter the existing publication.
*
- * This is dispatcher function for AlterPublicationOptions and
- * AlterPublicationTables.
+ * This is dispatcher function for AlterPublicationOptions,
+ * AlterPublicationSchemas and AlterPublicationTables.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -474,7 +715,41 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
if (stmt->options)
AlterPublicationOptions(pstate, stmt, rel, tup);
else
- AlterPublicationTables(stmt, rel, tup);
+ {
+ List *relations = NIL;
+ List *schemaidlist = NIL;
+
+ ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
+ &schemaidlist);
+
+ CheckAlterPublication(stmt, tup, relations, schemaidlist);
+
+ /*
+ * Lock the publication so nobody else can do anything with it. This
+ * prevents concurrent alter to add table(s) that were already going
+ * to become part of the publication by adding corresponding schema(s)
+ * via this command and similarly it will prevent the concurrent
+ * addition of schema(s) for which there is any corresponding table
+ * being added by this command.
+ */
+ LockDatabaseObject(PublicationRelationId, pubform->oid, 0,
+ AccessExclusiveLock);
+
+ /*
+ * It is possible that by the time we acquire the lock on publication,
+ * concurrent DDL has removed it. We can test this by checking the
+ * existence of publication.
+ */
+ if (!SearchSysCacheExists1(PUBLICATIONOID,
+ ObjectIdGetDatum(pubform->oid)))
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("publication \"%s\" does not exist",
+ stmt->pubname));
+
+ AlterPublicationTables(stmt, tup, relations, schemaidlist);
+ AlterPublicationSchemas(stmt, tup, schemaidlist);
+ }
/* Cleanup. */
heap_freetuple(tup);
@@ -551,10 +826,72 @@ RemovePublicationById(Oid pubid)
table_close(rel, RowExclusiveLock);
}
+/*
+ * Remove schema from publication by mapping OID.
+ */
+void
+RemovePublicationSchemaById(Oid psoid)
+{
+ Relation rel;
+ HeapTuple tup;
+ List *schemaRels = NIL;
+ Form_pg_publication_namespace pubsch;
+
+ rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE, ObjectIdGetDatum(psoid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u", psoid);
+
+ pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ /*
+ * Invalidate relcache so that publication info is rebuilt. See
+ * RemovePublicationRelById for why we need to consider all the
+ * partitions.
+ */
+ schemaRels = GetSchemaPublicationRelations(pubsch->pnnspid,
+ PUBLICATION_PART_ALL);
+ InvalidatePublicationRels(schemaRels);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Open relations specified by a relid list.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
+ */
+static List *
+OpenReliIdList(List *relids)
+{
+ ListCell *lc;
+ List *rels = NIL;
+
+ foreach(lc, relids)
+ {
+ PublicationRelInfo *pub_rel;
+ Oid relid = lfirst_oid(lc);
+ Relation rel = table_open(relid,
+ ShareUpdateExclusiveLock);
+
+ pub_rel = palloc(sizeof(PublicationRelInfo));
+ pub_rel->relation = rel;
+ rels = lappend(rels, pub_rel);
+ }
+
+ return rels;
+}
+
/*
* Open relations specified by a PublicationTable list.
- * In the returned list of PublicationRelInfo, tables are locked
- * in ShareUpdateExclusiveLock mode in order to add them to a publication.
+ * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
+ * add them to a publication.
*/
static List *
OpenTableList(List *tables)
@@ -658,6 +995,35 @@ CloseTableList(List *rels)
}
}
+/*
+ * Lock the schemas specified in the schema list in AccessShareLock mode in
+ * order to prevent concurrent schema deletion.
+ */
+static void
+LockSchemaList(List *schemalist)
+{
+ ListCell *lc;
+
+ foreach(lc, schemalist)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+ LockDatabaseObject(NamespaceRelationId, schemaid, 0, AccessShareLock);
+
+ /*
+ * It is possible that by the time we acquire the lock on schema,
+ * concurrent DDL has removed it. We can test this by checking the
+ * existence of schema.
+ */
+ if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(schemaid)))
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("schema with OID %u does not exist", schemaid));
+ }
+}
+
/*
* Add listed tables to the publication.
*/
@@ -727,6 +1093,68 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
}
}
+/*
+ * Add listed schemas to the publication.
+ */
+static void
+PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
+ AlterPublicationStmt *stmt)
+{
+ ListCell *lc;
+
+ Assert(!stmt || !stmt->for_all_tables);
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+ ObjectAddress obj;
+
+ obj = publication_add_schema(pubid, schemaid, if_not_exists);
+ if (stmt)
+ {
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
+
+ InvokeObjectPostCreateHook(PublicationNamespaceRelationId,
+ obj.objectId, 0);
+ }
+ }
+}
+
+/*
+ * Remove listed schemas from the publication.
+ */
+static void
+PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok)
+{
+ ObjectAddress obj;
+ ListCell *lc;
+ Oid psid;
+
+ foreach(lc, schemas)
+ {
+ Oid schemaid = lfirst_oid(lc);
+
+ psid = GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
+ Anum_pg_publication_namespace_oid,
+ ObjectIdGetDatum(schemaid),
+ ObjectIdGetDatum(pubid));
+ if (!OidIsValid(psid))
+ {
+ if (missing_ok)
+ continue;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tables from schema \"%s\" are not part of the publication",
+ get_namespace_name(schemaid))));
+ }
+
+ ObjectAddressSet(obj, PublicationNamespaceRelationId, psid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
/*
* Internal workhorse for changing a publication owner
*/
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 308e0adb55..53c18628a7 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_STATISTIC_EXT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1a2f159f24..857cc5ce6e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12286,6 +12286,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PUBLICATION:
+ case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
@@ -15994,6 +15995,33 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
+ /*
+ * Check that setting the relation to a different schema won't result in a
+ * publication having both a schema and the same schema's table, as this
+ * is not supported.
+ */
+ if (stmt->objectType == OBJECT_TABLE)
+ {
+ ListCell *lc;
+ List *schemaPubids = GetSchemaPublications(nspOid);
+ List *relPubids = GetRelationPublications(RelationGetRelid(rel));
+
+ foreach(lc, relPubids)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ if (list_member_oid(schemaPubids, pubid))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move table \"%s\" to schema \"%s\"",
+ RelationGetRelationName(rel), stmt->newschema),
+ errdetail("The schema \"%s\" and same schema's table \"%s\" cannot be part of the same publication \"%s\".",
+ stmt->newschema,
+ RelationGetRelationName(rel),
+ get_publication_name(pubid, false)));
+ }
+ }
+
/* common checks on switching namespaces */
CheckSetNamespace(oldNspOid, nspOid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70e9e54d3e..82464c9889 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4810,6 +4810,19 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
+static PublicationObjSpec *
+_copyPublicationObject(const PublicationObjSpec *from)
+{
+ PublicationObjSpec *newnode = makeNode(PublicationObjSpec);
+
+ COPY_SCALAR_FIELD(pubobjtype);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(pubtable);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static PublicationTable *
_copyPublicationTable(const PublicationTable *from)
{
@@ -4827,7 +4840,7 @@ _copyCreatePublicationStmt(const CreatePublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
@@ -4840,9 +4853,9 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
- COPY_NODE_FIELD(tables);
+ COPY_NODE_FIELD(pubobjects);
COPY_SCALAR_FIELD(for_all_tables);
- COPY_SCALAR_FIELD(tableAction);
+ COPY_SCALAR_FIELD(action);
return newnode;
}
@@ -5887,6 +5900,9 @@ copyObjectImpl(const void *from)
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
+ case T_PublicationObjSpec:
+ retval = _copyPublicationObject(from);
+ break;
case T_PublicationTable:
retval = _copyPublicationTable(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19eff20102..f537d3eb96 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2296,6 +2296,18 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
+static bool
+_equalPublicationObject(const PublicationObjSpec *a,
+ const PublicationObjSpec *b)
+{
+ COMPARE_SCALAR_FIELD(pubobjtype);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(pubtable);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
static bool
_equalPublicationTable(const PublicationTable *a, const PublicationTable *b)
{
@@ -2310,7 +2322,7 @@ _equalCreatePublicationStmt(const CreatePublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
return true;
@@ -2322,9 +2334,9 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
{
COMPARE_STRING_FIELD(pubname);
COMPARE_NODE_FIELD(options);
- COMPARE_NODE_FIELD(tables);
+ COMPARE_NODE_FIELD(pubobjects);
COMPARE_SCALAR_FIELD(for_all_tables);
- COMPARE_SCALAR_FIELD(tableAction);
+ COMPARE_SCALAR_FIELD(action);
return true;
}
@@ -3894,6 +3906,9 @@ equal(const void *a, const void *b)
case T_PartitionCmd:
retval = _equalPartitionCmd(a, b);
break;
+ case T_PublicationObjSpec:
+ retval = _equalPublicationObject(a, b);
+ break;
case T_PublicationTable:
retval = _equalPublicationTable(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..d0eb80e69c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,12 +195,17 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
static List *mergeTableFuncParameters(List *func_args, List *columns);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
+static RangeVar *makeRangeVarFromQualifiedName(char *name, List *rels,
+ int location,
+ core_yyscan_t yyscanner);
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
+static void preprocess_pubobj_list(List *pubobjspec_list,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -256,6 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
+ PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
struct GroupClause *groupclause;
@@ -425,14 +431,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
- drop_option_list publication_table_list
+ drop_option_list pub_obj_list
%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
-%type <node> opt_publication_for_tables publication_for_tables publication_table
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -517,6 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> table_ref
%type <jexpr> joined_table
%type <range> relation_expr
+%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
%type <target> target_el set_target insert_column_item
@@ -553,6 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
+%type <publicationobjectspec> PublicationObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -9591,69 +9598,131 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
/*****************************************************************************
*
- * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
+ * CREATE PUBLICATION name [WITH options]
+ *
+ * CREATE PUBLICATION FOR ALL TABLES [WITH options]
+ *
+ * CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
+ *
+ * pub_obj is one of:
+ *
+ * TABLE table [, ...]
+ * ALL TABLES IN SCHEMA schema [, ...]
*
*****************************************************************************/
CreatePublicationStmt:
- CREATE PUBLICATION name opt_publication_for_tables opt_definition
+ CREATE PUBLICATION name opt_definition
{
CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
n->pubname = $3;
- n->options = $5;
- if ($4 != NULL)
- {
- /* FOR TABLE */
- if (IsA($4, List))
- n->tables = (List *)$4;
- /* FOR ALL TABLES */
- else
- n->for_all_tables = true;
- }
+ n->options = $4;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR ALL TABLES opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $7;
+ n->for_all_tables = true;
+ $$ = (Node *)n;
+ }
+ | CREATE PUBLICATION name FOR pub_obj_list opt_definition
+ {
+ CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+ n->pubname = $3;
+ n->options = $6;
+ n->pubobjects = (List *)$5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
$$ = (Node *)n;
}
;
-opt_publication_for_tables:
- publication_for_tables { $$ = $1; }
- | /* EMPTY */ { $$ = NULL; }
- ;
-
-publication_for_tables:
- FOR TABLE publication_table_list
+/*
+ * FOR TABLE and FOR ALL TABLES IN SCHEMA specifications
+ *
+ * This rule parses publication objects with and without keyword prefixes.
+ *
+ * The actual type of the object without keyword prefix depends on the previous
+ * one with keyword prefix. It will be preprocessed in preprocess_pubobj_list().
+ *
+ * For the object without keyword prefix, we cannot just use relation_expr here,
+ * because some extended expressions in relation_expr cannot be used as a
+ * schemaname and we cannot differentiate it. So, we extract the rules from
+ * relation_expr here.
+ */
+PublicationObjSpec:
+ TABLE relation_expr
{
- $$ = (Node *) $3;
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = $2;
}
- | FOR ALL TABLES
+ | ALL TABLES IN_P SCHEMA ColId
{
- $$ = (Node *) makeInteger(true);
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->name = $5;
+ $$->location = @5;
}
- ;
+ | ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->location = @5;
+ }
+ | ColId
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->name = $1;
+ $$->location = @1;
+ }
+ | ColId indirection
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->location = @1;
+ }
+ /* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
+ | extended_relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->pubtable = makeNode(PublicationTable);
+ $$->pubtable->relation = $1;
+ }
+ | CURRENT_SCHEMA
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
+ $$->location = @1;
+ }
+ ;
-publication_table_list:
- publication_table
+pub_obj_list: PublicationObjSpec
{ $$ = list_make1($1); }
- | publication_table_list ',' publication_table
- { $$ = lappend($1, $3); }
- ;
-
-publication_table: relation_expr
- {
- PublicationTable *n = makeNode(PublicationTable);
- n->relation = $1;
- $$ = (Node *) n;
- }
+ | pub_obj_list ',' PublicationObjSpec
+ { $$ = lappend($1, $3); }
;
/*****************************************************************************
*
* ALTER PUBLICATION name SET ( options )
*
- * ALTER PUBLICATION name ADD TABLE table [, table2]
+ * ALTER PUBLICATION name ADD pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name DROP pub_obj [, ...]
+ *
+ * ALTER PUBLICATION name SET pub_obj [, ...]
*
- * ALTER PUBLICATION name DROP TABLE table [, table2]
+ * pub_obj is one of:
*
- * ALTER PUBLICATION name SET TABLE table [, table2]
+ * TABLE table_name [, ...]
+ * ALL TABLES IN SCHEMA schema_name [, ...]
*
*****************************************************************************/
@@ -9665,28 +9734,31 @@ AlterPublicationStmt:
n->options = $5;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name ADD_P TABLE publication_table_list
+ | ALTER PUBLICATION name ADD_P pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_ADD;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_ADD;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name SET TABLE publication_table_list
+ | ALTER PUBLICATION name SET pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_SET;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_SET;
$$ = (Node *)n;
}
- | ALTER PUBLICATION name DROP TABLE publication_table_list
+ | ALTER PUBLICATION name DROP pub_obj_list
{
AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
n->pubname = $3;
- n->tables = $6;
- n->tableAction = DEFELEM_DROP;
+ n->pubobjects = $5;
+ preprocess_pubobj_list(n->pubobjects, yyscanner);
+ n->action = DEFELEM_DROP;
$$ = (Node *)n;
}
;
@@ -12430,7 +12502,14 @@ relation_expr:
$$->inh = true;
$$->alias = NULL;
}
- | qualified_name '*'
+ | extended_relation_expr
+ {
+ $$ = $1;
+ }
+ ;
+
+extended_relation_expr:
+ qualified_name '*'
{
/* inheritance query, explicitly */
$$ = $1;
@@ -15104,28 +15183,7 @@ qualified_name:
}
| ColId indirection
{
- check_qualified_name($2, yyscanner);
- $$ = makeRangeVar(NULL, NULL, @1);
- switch (list_length($2))
- {
- case 1:
- $$->catalogname = NULL;
- $$->schemaname = $1;
- $$->relname = strVal(linitial($2));
- break;
- case 2:
- $$->catalogname = $1;
- $$->schemaname = strVal(linitial($2));
- $$->relname = strVal(lsecond($2));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("improper qualified name (too many dotted names): %s",
- NameListToString(lcons(makeString($1), $2))),
- parser_errposition(@1)));
- break;
- }
+ $$ = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
}
;
@@ -17102,6 +17160,43 @@ makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner)
return r;
}
+/*
+ * Convert a relation_name with name and namelist to a RangeVar using
+ * makeRangeVar.
+ */
+static RangeVar *
+makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
+ core_yyscan_t yyscanner)
+{
+ RangeVar *r;
+
+ check_qualified_name(namelist, yyscanner);
+ r = makeRangeVar(NULL, NULL, location);
+
+ switch (list_length(namelist))
+ {
+ case 1:
+ r->catalogname = NULL;
+ r->schemaname = name;
+ r->relname = strVal(linitial(namelist));
+ break;
+ case 2:
+ r->catalogname = name;
+ r->schemaname = strVal(linitial(namelist));
+ r->relname = strVal(lsecond(namelist));
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(lcons(makeString(name), namelist))),
+ parser_errposition(location));
+ break;
+ }
+
+ return r;
+}
+
/* Separate Constraint nodes from COLLATE clauses in a ColQualList */
static void
SplitColQualList(List *qualList,
@@ -17210,6 +17305,74 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
+/*
+ * Process pubobjspec_list to check for errors in any of the objects and
+ * convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
+ */
+static void
+preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
+{
+ ListCell *cell;
+ PublicationObjSpec *pubobj;
+ PublicationObjSpecType prevobjtype = PUBLICATIONOBJ_CONTINUATION;
+
+ if (!pubobjspec_list)
+ return;
+
+ pubobj = (PublicationObjSpec *) linitial(pubobjspec_list);
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ parser_errposition(pubobj->location));
+
+ foreach(cell, pubobjspec_list)
+ {
+ pubobj = (PublicationObjSpec *) lfirst(cell);
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
+ pubobj->pubobjtype = prevobjtype;
+
+ if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
+ {
+ /* relation name or pubtable must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubtable)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid table name at or near"),
+ parser_errposition(pubobj->location));
+ else if (pubobj->name)
+ {
+ /* convert it to PublicationTable */
+ PublicationTable *pubtable = makeNode(PublicationTable);
+ pubtable->relation = makeRangeVar(NULL, pubobj->name,
+ pubobj->location);
+ pubobj->pubtable = pubtable;
+ pubobj->name = NULL;
+ }
+ }
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ {
+ /*
+ * We can distinguish between the different type of schema
+ * objects based on whether name and pubtable is set.
+ */
+ if (pubobj->name)
+ pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ else if (!pubobj->name && !pubobj->pubtable)
+ pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ else
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid schema name at or near"),
+ parser_errposition(pubobj->location));
+ }
+
+ prevobjtype = pubobj->pubobjtype;
+ }
+}
+
/*----------
* Recursive view transformation
*
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 14d737fd93..6f6a203dea 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1068,6 +1068,9 @@ init_rel_sync_cache(MemoryContext cachectx)
CacheRegisterSyscacheCallback(PUBLICATIONRELMAP,
rel_sync_cache_publication_cb,
(Datum) 0);
+ CacheRegisterSyscacheCallback(PUBLICATIONNAMESPACEMAP,
+ rel_sync_cache_publication_cb,
+ (Datum) 0);
}
/*
@@ -1146,7 +1149,15 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
/* Validate the entry */
if (!entry->replicate_valid)
{
+ Oid schemaId = get_rel_namespace(relid);
List *pubids = GetRelationPublications(relid);
+
+ /*
+ * We don't acquire a lock on the namespace system table as we build
+ * the cache entry using a historic snapshot and all the later changes
+ * are absorbed while decoding WAL.
+ */
+ List *schemaPubids = GetSchemaPublications(schemaId);
ListCell *lc;
Oid publish_as_relid = relid;
@@ -1203,6 +1214,8 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
Oid ancestor = lfirst_oid(lc2);
if (list_member_oid(GetRelationPublications(ancestor),
+ pub->oid) ||
+ list_member_oid(GetSchemaPublications(get_rel_namespace(ancestor)),
pub->oid))
{
ancestor_published = true;
@@ -1212,7 +1225,9 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
}
}
- if (list_member_oid(pubids, pub->oid) || ancestor_published)
+ if (list_member_oid(pubids, pub->oid) ||
+ list_member_oid(schemaPubids, pub->oid) ||
+ ancestor_published)
publish = true;
}
@@ -1343,7 +1358,7 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid)
}
/*
- * Publication relation map syscache invalidation callback
+ * Publication relation/schema map syscache invalidation callback
*/
static void
rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b54c911766..9fa9e671a1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5550,6 +5550,7 @@ GetRelationPublicationActions(Relation relation)
List *puboids;
ListCell *lc;
MemoryContext oldcxt;
+ Oid schemaid;
PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
/*
@@ -5565,6 +5566,9 @@ GetRelationPublicationActions(Relation relation)
/* Fetch the publication membership info. */
puboids = GetRelationPublications(RelationGetRelid(relation));
+ schemaid = RelationGetNamespace(relation);
+ puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
+
if (relation->rd_rel->relispartition)
{
/* Add publications that the ancestors are in too. */
@@ -5577,6 +5581,9 @@ GetRelationPublicationActions(Relation relation)
puboids = list_concat_unique_oid(puboids,
GetRelationPublications(ancestor));
+ schemaid = get_rel_namespace(ancestor);
+ puboids = list_concat_unique_oid(puboids,
+ GetSchemaPublications(schemaid));
}
}
puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d6cb78dea8..56870b46e4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_range.h"
#include "catalog/pg_replication_origin.h"
@@ -617,6 +618,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACE */
+ PublicationNamespaceObjectIndexId,
+ 1,
+ {
+ Anum_pg_publication_namespace_oid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PublicationNamespaceRelationId, /* PUBLICATIONNAMESPACEMAP */
+ PublicationNamespacePnnspidPnpubidIndexId,
+ 2,
+ {
+ Anum_pg_publication_namespace_pnnspid,
+ Anum_pg_publication_namespace_pnpubid,
+ 0,
+ 0
+ },
+ 64
+ },
{PublicationRelationId, /* PUBLICATIONOID */
PublicationObjectIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index e38ff3c030..ecab0a9e4e 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -254,9 +254,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading publications");
(void) getPublications(fout, &numPublications);
- pg_log_info("reading publication membership");
+ pg_log_info("reading publication membership of tables");
getPublicationTables(fout, tblinfo, numTables);
+ pg_log_info("reading publication membership of schemas");
+ getPublicationNamespaces(fout);
+
pg_log_info("reading subscriptions");
getSubscriptions(fout);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 9b0e699ce8..2c4cfb9457 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2788,7 +2788,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
- strcmp(te->desc, "PUBLICATION TABLE") == 0))
+ strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
+ strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e9f68e8986..d1842edde0 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1875,14 +1875,15 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
}
/*
- * selectDumpablePublicationTable: policy-setting subroutine
- * Mark a publication table as to be dumped or not
+ * selectDumpablePublicationObject: policy-setting subroutine
+ * Mark a publication object as to be dumped or not
*
- * Publication tables have schemas, but those are ignored in decision making,
- * because publications are only dumped when we are dumping everything.
+ * A publication can have schemas and tables which have schemas, but those are
+ * ignored in decision making, because publications are only dumped when we are
+ * dumping everything.
*/
static void
-selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
+selectDumpablePublicationObject(DumpableObject *dobj, Archive *fout)
{
if (checkExtensionMembership(dobj, fout))
return; /* extension membership overrides all else */
@@ -4126,6 +4127,93 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
free(qpubname);
}
+/*
+ * getPublicationNamespaces
+ * get information about publication membership for dumpable schemas.
+ */
+void
+getPublicationNamespaces(Archive *fout)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ PublicationSchemaInfo *pubsinfo;
+ DumpOptions *dopt = fout->dopt;
+ int i_tableoid;
+ int i_oid;
+ int i_pnpubid;
+ int i_pnnspid;
+ int i,
+ j,
+ ntups;
+
+ if (dopt->no_publications || fout->remoteVersion < 150000)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Collect all publication membership info. */
+ appendPQExpBufferStr(query,
+ "SELECT tableoid, oid, pnpubid, pnnspid "
+ "FROM pg_catalog.pg_publication_namespace");
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_pnpubid = PQfnumber(res, "pnpubid");
+ i_pnnspid = PQfnumber(res, "pnnspid");
+
+ /* this allocation may be more than we need */
+ pubsinfo = pg_malloc(ntups * sizeof(PublicationSchemaInfo));
+ j = 0;
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid pnpubid = atooid(PQgetvalue(res, i, i_pnpubid));
+ Oid pnnspid = atooid(PQgetvalue(res, i, i_pnnspid));
+ PublicationInfo *pubinfo;
+ NamespaceInfo *nspinfo;
+
+ /*
+ * Ignore any entries for which we aren't interested in either the
+ * publication or the rel.
+ */
+ pubinfo = findPublicationByOid(pnpubid);
+ if (pubinfo == NULL)
+ continue;
+ nspinfo = findNamespaceByOid(pnnspid);
+ if (nspinfo == NULL)
+ continue;
+
+ /*
+ * We always dump publication namespaces unless the corresponding
+ * namespace is excluded from the dump.
+ */
+ if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
+ continue;
+
+ /* OK, make a DumpableObject for this relationship */
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&pubsinfo[j].dobj);
+ pubsinfo[j].dobj.namespace = nspinfo->dobj.namespace;
+ pubsinfo[j].dobj.name = nspinfo->dobj.name;
+ pubsinfo[j].publication = pubinfo;
+ pubsinfo[j].pubschema = nspinfo;
+
+ /* Decide whether we want to dump it */
+ selectDumpablePublicationObject(&(pubsinfo[j].dobj), fout);
+
+ j++;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
/*
* getPublicationTables
* get information about publication membership for dumpable tables.
@@ -4204,7 +4292,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
pubrinfo[j].pubtable = tbinfo;
/* Decide whether we want to dump it */
- selectDumpablePublicationTable(&(pubrinfo[j].dobj), fout);
+ selectDumpablePublicationObject(&(pubrinfo[j].dobj), fout);
j++;
}
@@ -4213,6 +4301,44 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(query);
}
+/*
+ * dumpPublicationNamespace
+ * dump the definition of the given publication schema mapping.
+ */
+static void
+dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
+{
+ NamespaceInfo *schemainfo = pubsinfo->pubschema;
+ PublicationInfo *pubinfo = pubsinfo->publication;
+ PQExpBuffer query;
+ char *tag;
+
+ if (!(pubsinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+ return;
+
+ tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
+
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ", fmtId(pubinfo->dobj.name));
+ appendPQExpBuffer(query, "ADD ALL TABLES IN SCHEMA %s;\n", fmtId(schemainfo->dobj.name));
+
+ /*
+ * There is no point in creating drop query as the drop is done by schema
+ * drop.
+ */
+ ArchiveEntry(fout, pubsinfo->dobj.catId, pubsinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = tag,
+ .namespace = schemainfo->dobj.name,
+ .owner = pubinfo->rolname,
+ .description = "PUBLICATION TABLES IN SCHEMA",
+ .section = SECTION_POST_DATA,
+ .createStmt = query->data));
+
+ free(tag);
+ destroyPQExpBuffer(query);
+}
+
/*
* dumpPublicationTable
* dump the definition of the given publication table mapping
@@ -10205,6 +10331,10 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ dumpPublicationNamespace(fout,
+ (const PublicationSchemaInfo *) dobj);
+ break;
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const SubscriptionInfo *) dobj);
break;
@@ -18429,6 +18559,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
+ case DO_PUBLICATION_REL_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index cc55e598ec..f9af14b793 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,6 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
+ DO_PUBLICATION_REL_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
@@ -631,6 +632,17 @@ typedef struct _PublicationRelInfo
TableInfo *pubtable;
} PublicationRelInfo;
+/*
+ * The PublicationSchemaInfo struct is used to represent publication schema
+ * mapping.
+ */
+typedef struct _PublicationSchemaInfo
+{
+ DumpableObject dobj;
+ PublicationInfo *publication;
+ NamespaceInfo *pubschema;
+} PublicationSchemaInfo;
+
/*
* The SubscriptionInfo struct is used to represent subscription.
*/
@@ -725,6 +737,7 @@ extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
extern PublicationInfo *getPublications(Archive *fout,
int *numPublications);
+extern void getPublicationNamespaces(Archive *fout);
extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
extern void getSubscriptions(Archive *fout);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 46461fb6a1..9901d9e0ba 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,6 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
+ PRIO_PUBLICATION_REL_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -135,6 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
+ PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1477,6 +1479,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_PUBLICATION_REL_IN_SCHEMA:
+ snprintf(buf, bufsize,
+ "PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_SUBSCRIPTION:
snprintf(buf, bufsize,
"SUBSCRIPTION (ID %d OID %u)",
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 2eece79250..d293f52b05 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2321,6 +2321,15 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub3' => {
+ create_order => 50,
+ create_sql => 'CREATE PUBLICATION pub3;',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub3 WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -2357,6 +2366,27 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test' => {
+ create_order => 51,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA dump_test;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public' => {
+ create_order => 52,
+ create_sql =>
+ 'ALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;',
+ regexp => qr/^
+ \QALTER PUBLICATION pub3 ADD ALL TABLES IN SCHEMA public;\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SCHEMA public' => {
regexp => qr/^CREATE SCHEMA public;/m,
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c..c7f97476d1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3146,17 +3146,40 @@ describeOneTableDetails(const char *schemaname,
/* print any publications */
if (pset.sversion >= 100000)
{
- printfPQExpBuffer(&buf,
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
- "WHERE pr.prrelid = '%s'\n"
- "UNION ALL\n"
- "SELECT pubname\n"
- "FROM pg_catalog.pg_publication p\n"
- "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
- "ORDER BY 1;",
- oid, oid);
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid\n"
+ "WHERE pc.oid ='%s' and pg_catalog.pg_relation_is_publishable('%s')\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid, oid, oid);
+ }
+ else
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s'\n"
+ "UNION ALL\n"
+ "SELECT pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
+ "ORDER BY 1;",
+ oid, oid);
+ }
result = PSQLexec(buf.data);
if (!result)
@@ -5020,6 +5043,8 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
+ int pub_schema_tuples = 0;
+ char **footers = NULL;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
@@ -5052,17 +5077,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
- termPQExpBuffer(&buf);
if (!res)
+ {
+ termPQExpBuffer(&buf);
return false;
+ }
myopt.nullPrint = NULL;
myopt.title = _("List of schemas");
myopt.translate_header = true;
+ if (pattern && pset.sversion >= 150000)
+ {
+ PGresult *result;
+ int i;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pubname \n"
+ "FROM pg_catalog.pg_publication p\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid \n"
+ "WHERE n.nspname = '%s'\n"
+ "ORDER BY 1",
+ pattern);
+ result = PSQLexec(buf.data);
+ if (!result)
+ {
+ termPQExpBuffer(&buf);
+ return false;
+ }
+ else
+ pub_schema_tuples = PQntuples(result);
+
+ if (pub_schema_tuples > 0)
+ {
+ /*
+ * Allocate memory for footers. Size of footers will be 1 (for
+ * storing "Publications:" string) + publication schema mapping
+ * count + 1 (for storing NULL).
+ */
+ footers = (char **) palloc((1 + pub_schema_tuples + 1) * sizeof(char *));
+ footers[0] = pstrdup(_("Publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < pub_schema_tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ footers[i + 1] = pstrdup(buf.data);
+ }
+
+ footers[i + 1] = NULL;
+ myopt.footers = footers;
+ }
+
+ PQclear(result);
+ }
+
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ termPQExpBuffer(&buf);
PQclear(res);
+
+ /* Free the memory allocated for the footer */
+ if (footers)
+ {
+ char **footer = NULL;
+
+ for (footer = footers; *footer; footer++)
+ pfree(*footer);
+
+ pfree(footers);
+ }
+
return true;
}
@@ -6209,6 +6297,41 @@ listPublications(const char *pattern)
return true;
}
+/*
+ * Add footer to publication description.
+ */
+static bool
+addFooterToPublicationDesc(PQExpBuffer buf, char *footermsg,
+ bool singlecol, printTableContent *cont)
+{
+ PGresult *res;
+ int count = 0;
+ int i = 0;
+
+ res = PSQLexec(buf->data);
+ if (!res)
+ return false;
+ else
+ count = PQntuples(res);
+
+ if (count > 0)
+ printTableAddFooter(cont, _(footermsg));
+
+ for (i = 0; i < count; i++)
+ {
+ if (!singlecol)
+ printfPQExpBuffer(buf, " \"%s.%s\"", PQgetvalue(res, i, 0),
+ PQgetvalue(res, i, 1));
+ else
+ printfPQExpBuffer(buf, " \"%s\"", PQgetvalue(res, i, 0));
+
+ printTableAddFooter(cont, buf->data);
+ }
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dRp+
* Describes publications including the contents.
@@ -6224,6 +6347,9 @@ describePublications(const char *pattern)
bool has_pubtruncate;
bool has_pubviaroot;
+ PQExpBufferData title;
+ printTableContent cont;
+
if (pset.sversion < 100000)
{
char sverbuf[32];
@@ -6286,15 +6412,10 @@ describePublications(const char *pattern)
const char align = 'l';
int ncols = 5;
int nrows = 1;
- int tables = 0;
- PGresult *tabres;
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
- int j;
- PQExpBufferData title;
printTableOpt myopt = pset.popt.topt;
- printTableContent cont;
if (has_pubtruncate)
ncols++;
@@ -6327,6 +6448,7 @@ describePublications(const char *pattern)
if (!puballtables)
{
+ /* Get the tables for the specified publication */
printfPQExpBuffer(&buf,
"SELECT n.nspname, c.relname\n"
"FROM pg_catalog.pg_class c,\n"
@@ -6336,31 +6458,22 @@ describePublications(const char *pattern)
" AND c.oid = pr.prrelid\n"
" AND pr.prpubid = '%s'\n"
"ORDER BY 1,2", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables:", false, &cont))
+ goto error_return;
- tabres = PSQLexec(buf.data);
- if (!tabres)
- {
- printTableCleanup(&cont);
- PQclear(res);
- termPQExpBuffer(&buf);
- termPQExpBuffer(&title);
- return false;
- }
- else
- tables = PQntuples(tabres);
-
- if (tables > 0)
- printTableAddFooter(&cont, _("Tables:"));
-
- for (j = 0; j < tables; j++)
+ if (pset.sversion >= 150000)
{
- printfPQExpBuffer(&buf, " \"%s.%s\"",
- PQgetvalue(tabres, j, 0),
- PQgetvalue(tabres, j, 1));
-
- printTableAddFooter(&cont, buf.data);
+ /* Get the schemas for the specified publication */
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname\n"
+ "FROM pg_catalog.pg_namespace n\n"
+ " JOIN pg_catalog.pg_publication_namespace pn ON n.oid = pn.pnnspid\n"
+ "WHERE pn.pnpubid = '%s'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, "Tables from schemas:",
+ true, &cont))
+ goto error_return;
}
- PQclear(tabres);
}
printTable(&cont, pset.queryFout, false, pset.logfile);
@@ -6373,6 +6486,13 @@ describePublications(const char *pattern)
PQclear(res);
return true;
+
+error_return:
+ printTableCleanup(&cont);
+ PQclear(res);
+ termPQExpBuffer(&buf);
+ termPQExpBuffer(&title);
+ return false;
}
/*
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8ed..8e01f54500 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1644,10 +1644,22 @@ psql_completion(const char *text, int start, int end)
/* ALTER PUBLICATION <name> */
else if (Matches("ALTER", "PUBLICATION", MatchAny))
- COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+ COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME TO", "SET");
+ /* ALTER PUBLICATION <name> ADD */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
+ /* ALTER PUBLICATION <name> DROP */
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "DROP"))
+ COMPLETE_WITH("ALL TABLES IN SCHEMA", "TABLE");
/* ALTER PUBLICATION <name> SET */
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
- COMPLETE_WITH("(", "TABLE");
+ COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA'");
/* ALTER PUBLICATION <name> SET ( */
else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -2688,17 +2700,31 @@ psql_completion(const char *text, int start, int end)
/* CREATE PUBLICATION */
else if (Matches("CREATE", "PUBLICATION", MatchAny))
- COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
+ COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "FOR ALL TABLES IN SCHEMA", "WITH (");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
- COMPLETE_WITH("TABLE", "ALL TABLES");
+ COMPLETE_WITH("TABLE", "ALL TABLES", "ALL TABLES IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL"))
- COMPLETE_WITH("TABLES");
- else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES")
- || Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
+ COMPLETE_WITH("TABLES", "TABLES IN SCHEMA");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES"))
+ COMPLETE_WITH("IN SCHEMA", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny))
COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+ /*
+ * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
+ * ..."
+ */
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " AND nspname != 'pg_catalog' "
+ " AND nspname not like 'pg\\_toast%%' "
+ " AND nspname not like 'pg\\_temp%%' "
+ " UNION SELECT 'CURRENT_SCHEMA' ");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
+ COMPLETE_WITH("WITH (");
/* Complete "CREATE PUBLICATION <name> [...] WITH" */
else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
COMPLETE_WITH("publish", "publish_via_partition_root");
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2885f35ccd..3eca295ff4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -122,6 +122,7 @@ typedef enum ObjectClass
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_POLICY, /* pg_policy */
OCLASS_PUBLICATION, /* pg_publication */
+ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
OCLASS_PUBLICATION_REL, /* pg_publication_rel */
OCLASS_SUBSCRIPTION, /* pg_subscription */
OCLASS_TRANSFORM /* pg_transform */
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 82f2536c65..1ae439e6f3 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -111,6 +111,12 @@ typedef enum PublicationPartOpt
extern List *GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(bool pubviaroot);
+extern List *GetPublicationSchemas(Oid pubid);
+extern List *GetSchemaPublications(Oid schemaid);
+extern List *GetSchemaPublicationRelations(Oid schemaid,
+ PublicationPartOpt pub_partopt);
+extern List *GetAllSchemaPublicationRelations(Oid puboid,
+ PublicationPartOpt pub_partopt);
extern List *GetPubPartitionOptionRelations(List *result,
PublicationPartOpt pub_partopt,
Oid relid);
@@ -118,6 +124,8 @@ extern List *GetPubPartitionOptionRelations(List *result,
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *targetrel,
bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
extern Oid get_publication_oid(const char *pubname, bool missing_ok);
extern char *get_publication_name(Oid pubid, bool missing_ok);
diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h
new file mode 100644
index 0000000000..b7e16af819
--- /dev/null
+++ b/src/include/catalog/pg_publication_namespace.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_namespace.h
+ * definition of the system catalog for mappings between schemas and
+ * publications (pg_publication_namespace)
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_namespace.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_NAMESPACE_H
+#define PG_PUBLICATION_NAMESPACE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_publication_namespace_d.h"
+
+
+/* ----------------
+ * pg_publication_namespace definition. cpp turns this into
+ * typedef struct FormData_pg_publication_namespace
+ * ----------------
+ */
+CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId)
+{
+ Oid oid; /* oid */
+ Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */
+ Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */
+} FormData_pg_publication_namespace;
+
+/* ----------------
+ * Form_pg_publication_namespace corresponds to a pointer to a tuple with
+ * the format of pg_publication_namespace relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_namespace *Form_pg_publication_namespace;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops));
+
+#endif /* PG_PUBLICATION_NAMESPACE_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 77a299bb18..4ba68c70ee 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,6 +26,7 @@ extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
extern void RemovePublicationRelById(Oid proid);
+extern void RemovePublicationSchemaById(Oid psoid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 541e9861ba..7c657c1241 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -487,6 +487,7 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
+ T_PublicationObjSpec,
T_PublicationTable,
/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3138877553..49123e28a4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1816,6 +1816,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
+ OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
OBJECT_ROUTINE,
@@ -3642,12 +3643,32 @@ typedef struct PublicationTable
RangeVar *relation; /* relation to be published */
} PublicationTable;
+/*
+ * Publication object type
+ */
+typedef enum PublicationObjSpecType
+{
+ PUBLICATIONOBJ_TABLE, /* Table type */
+ PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
+} PublicationObjSpecType;
+
+typedef struct PublicationObjSpec
+{
+ NodeTag type;
+ PublicationObjSpecType pubobjtype; /* type of this publication object */
+ char *name;
+ PublicationTable *pubtable;
+ int location; /* token location, or -1 if unknown */
+} PublicationObjSpec;
+
typedef struct CreatePublicationStmt
{
NodeTag type;
char *pubname; /* Name of the publication */
List *options; /* List of DefElem nodes */
- List *tables; /* Optional list of tables to add */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
} CreatePublicationStmt;
@@ -3659,10 +3680,14 @@ typedef struct AlterPublicationStmt
/* parameters used for ALTER PUBLICATION ... WITH */
List *options; /* List of DefElem nodes */
- /* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
- List *tables; /* List of tables to add/drop */
+ /*
+ * Parameters used for ALTER PUBLICATION ... ADD/DROP/SET publication
+ * objects.
+ */
+ List *pubobjects; /* Optional list of publication objects */
bool for_all_tables; /* Special publication for all tables in db */
- DefElemAction tableAction; /* What action to perform with the tables */
+ DefElemAction action; /* What action to perform with the
+ * tables/schemas */
} AlterPublicationStmt;
typedef struct CreateSubscriptionStmt
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d74a348600..c8cfbc30f6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -76,6 +76,8 @@ enum SysCacheIdentifier
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
+ PUBLICATIONNAMESPACE,
+ PUBLICATIONNAMESPACEMAP,
PUBLICATIONOID,
PUBLICATIONREL,
PUBLICATIONRELMAP,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c3b2b37067..24d1c7cd28 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -4557,3 +4557,17 @@ DETAIL: Failing row contains (2, 1).
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+ERROR: cannot move table "t1" to schema "alter2"
+DETAIL: The schema "alter2" and same schema's table "t1" cannot be part of the same publication "pub1".
+drop publication pub1;
+drop schema alter1 cascade;
+NOTICE: drop cascades to table alter1.t1
+drop schema alter2 cascade;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 388097a695..a9e7f2eed5 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -45,6 +45,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
@@ -427,6 +428,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -490,7 +492,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | regress_addr_sub | regress_addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | addr_nsp.gentable in publication addr_pub | t
-(49 rows)
+ publication namespace | | | addr_nsp in publication addr_pub_schema | t
+(50 rows)
---
--- Cleanup resources
@@ -502,6 +505,7 @@ drop cascades to foreign table genftable
drop cascades to server integer
drop cascades to user mapping for regress_addr_user on server integer
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
NOTICE: drop cascades to 14 other objects
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 1461e947cd..215eb899be 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -258,6 +258,8 @@ NOTICE: checking pg_transform {trftosql} => pg_proc {oid}
NOTICE: checking pg_sequence {seqrelid} => pg_class {oid}
NOTICE: checking pg_sequence {seqtypid} => pg_type {oid}
NOTICE: checking pg_publication {pubowner} => pg_authid {oid}
+NOTICE: checking pg_publication_namespace {pnpubid} => pg_publication {oid}
+NOTICE: checking pg_publication_namespace {pnnspid} => pg_namespace {oid}
NOTICE: checking pg_publication_rel {prpubid} => pg_publication {oid}
NOTICE: checking pg_publication_rel {prrelid} => pg_class {oid}
NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 82bce9be09..0f4fe4db8f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -69,6 +69,78 @@ DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications.
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL: Tables from schema cannot be added to, dropped from, or set on FOR ALL TABLES publications.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL 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
+Tables:
+ "public.testpub_tbl1"
+Tables from schemas:
+ "pub_test"
+
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL 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
+Tables:
+ "public.testpub_tbl1"
+
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL 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
+Tables from schemas:
+ "pub_test"
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+ERROR: cannot add relation "pub_test.testpub_nopk" to publication
+DETAIL: Table's schema "pub_test" is already part of the publication or part of the specified schema list.
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+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
+Tables:
+ "pub_test.testpub_nopk"
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
pubname | puballtables
----------------------+--------------
@@ -94,7 +166,7 @@ Publications:
(1 row)
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
SET client_min_messages = 'ERROR';
@@ -270,18 +342,23 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to create FOR ALL TABLES IN SCHEMA publication
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
ERROR: must be owner of table testpub_tbl1
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
+ERROR: must be superuser to add or set schemas
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
Publication testpub_default
@@ -313,11 +390,452 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
testpub_default | regress_publication_user2 | f | t | t | t | f | f
(1 row)
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+
+CREATE PUBLICATION testpub2_forschema FOR ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+ "pub_test3"
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+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
+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
+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
+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
+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
+Tables:
+ "CURRENT_SCHEMA.CURRENT_SCHEMA"
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+ERROR: no schema has been selected for CURRENT_SCHEMA
+RESET SEARCH_PATH;
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+ ^
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+ERROR: syntax error at or near "CURRENT_SCHEMA"
+LINE 1: CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHE...
+ ^
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+ERROR: schema "non_existent_schema" does not exist
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+ERROR: cannot add schema "pg_catalog" to publication
+DETAIL: This operation is not supported for system schemas.
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- 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
+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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL 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
+(1 row)
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+ "pub_test2"
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+NOTICE: drop cascades to table "CURRENT_SCHEMA"."CURRENT_SCHEMA"
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ERROR: cannot update table "tbl" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+DROP PUBLICATION testpubpart_forschema;
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+ERROR: cannot update table "child_parent1" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart2.parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+UPDATE pub_testpart1.child_parent2 set a = 1;
+ERROR: cannot update table "child_parent2" because it does not have a replica identity and publishes updates
+HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+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
+(1 row)
+
+ALTER PUBLICATION testpub3_forschema SET ALL 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
+Tables from schemas:
+ "pub_test1"
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL 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
+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
+Tables:
+ "pub_test2.tbl1"
+Tables from schemas:
+ "pub_test1"
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+ ^
+DROP VIEW testpub_view;
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
NOTICE: drop cascades to table pub_test.testpub_nopk
+DROP SCHEMA pub_test1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_test1.tbl
+drop cascades to table pub_test1.tbl1
+DROP SCHEMA pub_test2 CASCADE;
+NOTICE: drop cascades to table pub_test2.tbl1
+DROP SCHEMA pub_testpart1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table pub_testpart1.parent1
+drop cascades to table pub_testpart1.child_parent2
+DROP SCHEMA pub_testpart2 CASCADE;
+NOTICE: drop cascades to table pub_testpart2.parent2
+-- Test the list of partitions published with or without
+-- 'PUBLISH_VIA_PARTITION_ROOT' parameter
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+------------
+ pub | sch2 | tbl1_part1
+(1 row)
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+ pubname | schemaname | tablename
+---------+------------+-----------
+ pub | sch1 | tbl1
+(1 row)
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
DROP ROLE regress_publication_user_dummy;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 982b6aff53..d04dc66db9 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -140,6 +140,7 @@ pg_partitioned_table|t
pg_policy|t
pg_proc|t
pg_publication|t
+pg_publication_namespace|t
pg_publication_rel|t
pg_range|t
pg_replication_origin|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 4ddbd16a4e..5fac2585d9 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2990,3 +2990,15 @@ insert into attach_parted_part1 values (2, 1);
-- ...and doesn't when the partition is detached along with its own partition
alter table target_parted detach partition attach_parted;
insert into attach_parted_part1 values (2, 1);
+
+-- Test altering table having publication
+create schema alter1;
+create schema alter2;
+create table alter1.t1 (a int);
+set client_min_messages = 'ERROR';
+create publication pub1 for table alter1.t1, all tables in schema alter2;
+reset client_min_messages;
+alter table alter1.t1 set schema alter2; -- should fail
+drop publication pub1;
+drop schema alter1 cascade;
+drop schema alter2 cascade;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 2f4f66e3e1..2f40156eb4 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -48,6 +48,7 @@ CREATE TRANSFORM FOR int LANGUAGE SQL (
-- suppress warning that depends on wal_level
SET client_min_messages = 'ERROR';
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
+CREATE PUBLICATION addr_pub_schema FOR ALL TABLES IN SCHEMA addr_nsp;
RESET client_min_messages;
CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);
CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
@@ -197,6 +198,7 @@ WITH objects (type, name, args) AS (VALUES
('transform', '{int}', '{sql}'),
('access method', '{btree}', '{}'),
('publication', '{addr_pub}', '{}'),
+ ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'),
('publication relation', '{addr_nsp, gentable}', '{addr_pub}'),
('subscription', '{regress_addr_sub}', '{}'),
('statistics object', '{addr_nsp, gentable_stat}', '{}')
@@ -215,6 +217,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP PUBLICATION addr_pub;
+DROP PUBLICATION addr_pub_schema;
DROP SUBSCRIPTION regress_addr_sub;
DROP SCHEMA addr_nsp CASCADE;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index e5745d575b..85a5302a74 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -51,12 +51,46 @@ ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-- fail - can't add to for all tables publication
ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
+-- fail - can't add schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables ADD ALL TABLES IN SCHEMA pub_test;
+-- fail - can't drop schema from 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables DROP ALL TABLES IN SCHEMA pub_test;
+-- fail - can't set schema to 'FOR ALL TABLES' publication
+ALTER PUBLICATION testpub_foralltables SET ALL TABLES IN SCHEMA pub_test;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_fortable FOR TABLE testpub_tbl1;
+RESET client_min_messages;
+-- should be able to add schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable ADD ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to drop schema from 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable DROP ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+-- should be able to set schema to 'FOR TABLE' publication
+ALTER PUBLICATION testpub_fortable SET ALL TABLES IN SCHEMA pub_test;
+\dRp+ testpub_fortable
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pub_test;
+RESET client_min_messages;
+-- fail - can't create publication with schema and table of the same schema
+CREATE PUBLICATION testpub_for_tbl_schema FOR ALL TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
+-- fail - can't add a table of the same schema to the schema publication
+ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
+-- fail - can't drop a table from the schema publication which isn't in the
+-- publication
+ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
+-- should be able to set table to schema publication
+ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
+\dRp+ testpub_forschema
+
SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
\d+ testpub_tbl2
\dRp+ testpub_foralltables
DROP TABLE testpub_tbl2;
-DROP PUBLICATION testpub_foralltables;
+DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema;
CREATE TABLE testpub_tbl3 (a int);
CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3);
@@ -154,9 +188,12 @@ GRANT CREATE ON DATABASE regression TO regress_publication_user2;
SET ROLE regress_publication_user2;
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub2; -- ok
+CREATE PUBLICATION testpub3 FOR ALL TABLES IN SCHEMA pub_test; -- fail
+CREATE PUBLICATION testpub3; -- ok
RESET client_min_messages;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail
+ALTER PUBLICATION testpub3 ADD ALL TABLES IN SCHEMA pub_test; -- fail
SET ROLE regress_publication_user;
GRANT regress_publication_user TO regress_publication_user2;
@@ -164,12 +201,12 @@ SET ROLE regress_publication_user2;
ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
DROP PUBLICATION testpub2;
+DROP PUBLICATION testpub3;
SET ROLE regress_publication_user;
REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
-DROP VIEW testpub_view;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
@@ -190,11 +227,251 @@ ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
+-- adding schemas and tables
+CREATE SCHEMA pub_test1;
+CREATE SCHEMA pub_test2;
+CREATE SCHEMA pub_test3;
+CREATE SCHEMA "CURRENT_SCHEMA";
+CREATE TABLE pub_test1.tbl (id int, data text);
+CREATE TABLE pub_test1.tbl1 (id serial primary key, data text);
+CREATE TABLE pub_test2.tbl1 (id serial primary key, data text);
+CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
+
+-- suppress warning that depends on wal_level
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub1_forschema FOR ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+CREATE PUBLICATION testpub2_forschema FOR ALL TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
+\dRp+ testpub2_forschema
+
+-- check create publication on CURRENT_SCHEMA
+CREATE PUBLICATION testpub3_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+CREATE PUBLICATION testpub4_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub5_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA, "CURRENT_SCHEMA";
+CREATE PUBLICATION testpub6_forschema FOR ALL TABLES IN SCHEMA "CURRENT_SCHEMA", CURRENT_SCHEMA;
+CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
+
+RESET client_min_messages;
+
+\dRp+ testpub3_forschema
+\dRp+ testpub4_forschema
+\dRp+ testpub5_forschema
+\dRp+ testpub6_forschema
+\dRp+ testpub_fortable
+
+-- check create publication on CURRENT_SCHEMA where search_path is not set
+SET SEARCH_PATH='';
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA CURRENT_SCHEMA;
+RESET SEARCH_PATH;
+
+-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
+-- is not specified
+CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
+
+-- check create publication on CURRENT_SCHEMA along with FOR TABLE
+CREATE PUBLICATION testpub_forschema1 FOR TABLE CURRENT_SCHEMA;
+
+-- check create publication on a schema that does not exist
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA non_existent_schema;
+
+-- check create publication on a system schema
+CREATE PUBLICATION testpub_forschema FOR ALL TABLES IN SCHEMA pg_catalog;
+
+-- check create publication on an object which is not schema
+CREATE PUBLICATION testpub1_forschema1 FOR ALL TABLES IN SCHEMA testpub_view;
+
+-- dropping the schema should reflect the change in publication
+DROP SCHEMA pub_test3;
+\dRp+ testpub2_forschema
+
+-- renaming the schema should reflect the change in publication
+ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
+\dRp+ testpub2_forschema
+
+ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
+\dRp+ testpub2_forschema
+
+-- alter publication add schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- add non existent schema
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- add a schema which is already added to the publication
+ALTER PUBLICATION testpub1_forschema ADD ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication drop schema
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop schema that is not present in the publication
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test2;
+\dRp+ testpub1_forschema
+
+-- drop a schema that does not exist in the system
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- drop all schemas
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub1_forschema
+
+-- alter publication set multiple schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test2;
+\dRp+ testpub1_forschema
+
+-- alter publication set non-existent schema
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA non_existent_schema;
+\dRp+ testpub1_forschema
+
+-- alter publication set it duplicate schemas should set the schemas after
+-- removing the duplicate schemas
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1, pub_test1;
+\dRp+ testpub1_forschema
+
+-- cleanup pub_test1 schema for invalidation tests
+ALTER PUBLICATION testpub2_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+DROP PUBLICATION testpub3_forschema, testpub4_forschema, testpub5_forschema, testpub6_forschema, testpub_fortable;
+DROP SCHEMA "CURRENT_SCHEMA" CASCADE;
+
+-- verify relation cache invalidations through update statement for the
+-- default REPLICA IDENTITY on the relation, if schema is part of the
+-- publication then update will fail because relation's relreplident
+-- option will be set, if schema is not part of the publication then update
+-- will be successful.
+INSERT INTO pub_test1.tbl VALUES(1, 'test');
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema DROP ALL TABLES IN SCHEMA pub_test1;
+
+-- success
+UPDATE pub_test1.tbl SET id = 2;
+ALTER PUBLICATION testpub1_forschema SET ALL TABLES IN SCHEMA pub_test1;
+
+-- fail
+UPDATE pub_test1.tbl SET id = 2;
+
+-- verify invalidation of partition table having parent and child tables in
+-- different schema
+CREATE SCHEMA pub_testpart1;
+CREATE SCHEMA pub_testpart2;
+
+CREATE TABLE pub_testpart1.parent1 (a int) partition by list (a);
+CREATE TABLE pub_testpart2.child_parent1 partition of pub_testpart1.parent1 for values in (1);
+INSERT INTO pub_testpart2.child_parent1 values(1);
+UPDATE pub_testpart2.child_parent1 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart1;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart1.parent1 set a = 1;
+UPDATE pub_testpart2.child_parent1 set a = 1;
+
+DROP PUBLICATION testpubpart_forschema;
+
+-- verify invalidation of partition tables for schema publication that has
+-- parent and child tables of different partition hierarchies
+CREATE TABLE pub_testpart2.parent2 (a int) partition by list (a);
+CREATE TABLE pub_testpart1.child_parent2 partition of pub_testpart2.parent2 for values in (1);
+INSERT INTO pub_testpart1.child_parent2 values(1);
+UPDATE pub_testpart1.child_parent2 set a = 1;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpubpart_forschema FOR ALL TABLES IN SCHEMA pub_testpart2;
+RESET client_min_messages;
+
+-- fail
+UPDATE pub_testpart2.child_parent1 set a = 1;
+UPDATE pub_testpart2.parent2 set a = 1;
+UPDATE pub_testpart1.child_parent2 set a = 1;
+
+-- alter publication set 'ALL TABLES IN SCHEMA' on an empty publication.
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub3_forschema;
+RESET client_min_messages;
+\dRp+ testpub3_forschema
+ALTER PUBLICATION testpub3_forschema SET ALL TABLES IN SCHEMA pub_test1;
+\dRp+ testpub3_forschema
+
+-- create publication including both 'FOR TABLE' and 'FOR ALL TABLES IN SCHEMA'
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forschema_fortable FOR ALL TABLES IN SCHEMA pub_test1, TABLE pub_test2.tbl1;
+CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, ALL TABLES IN SCHEMA pub_test1;
+RESET client_min_messages;
+
+\dRp+ testpub_forschema_fortable
+\dRp+ testpub_fortable_forschema
+
+-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
+--'FOR TABLE' or 'FOR ALL TABLES'
+CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
+
+DROP VIEW testpub_view;
+
DROP PUBLICATION testpub_default;
DROP PUBLICATION testpib_ins_trunct;
DROP PUBLICATION testpub_fortbl;
+DROP PUBLICATION testpub1_forschema;
+DROP PUBLICATION testpub2_forschema;
+DROP PUBLICATION testpub3_forschema;
+DROP PUBLICATION testpub_forschema_fortable;
+DROP PUBLICATION testpub_fortable_forschema;
+DROP PUBLICATION testpubpart_forschema;
DROP SCHEMA pub_test CASCADE;
+DROP SCHEMA pub_test1 CASCADE;
+DROP SCHEMA pub_test2 CASCADE;
+DROP SCHEMA pub_testpart1 CASCADE;
+DROP SCHEMA pub_testpart2 CASCADE;
+
+-- Test the list of partitions published with or without
+-- 'PUBLISH_VIA_PARTITION_ROOT' parameter
+SET client_min_messages = 'ERROR';
+CREATE SCHEMA sch1;
+CREATE SCHEMA sch2;
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch2.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Schema publication that does not include the schema that has the parent table
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch2 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+-- Table publication that does not include the parent table
+CREATE PUBLICATION pub FOR TABLE sch2.tbl1_part1 WITH (PUBLISH_VIA_PARTITION_ROOT=0);
+SELECT * FROM pg_publication_tables;
+
+DROP PUBLICATION pub;
+DROP TABLE sch2.tbl1_part1;
+DROP TABLE sch1.tbl1;
+
+CREATE TABLE sch1.tbl1 (a int) PARTITION BY RANGE(a);
+CREATE TABLE sch1.tbl1_part1 PARTITION OF sch1.tbl1 FOR VALUES FROM (1) to (10);
+CREATE TABLE sch1.tbl1_part2 PARTITION OF sch1.tbl1 FOR VALUES FROM (10) to (20);
+CREATE TABLE sch1.tbl1_part3 (a int) PARTITION BY RANGE(a);
+ALTER TABLE sch1.tbl1 ATTACH PARTITION sch1.tbl1_part3 FOR VALUES FROM (20) to (30);
+CREATE PUBLICATION pub FOR ALL TABLES IN SCHEMA sch1 WITH (PUBLISH_VIA_PARTITION_ROOT=1);
+SELECT * FROM pg_publication_tables;
+
+RESET client_min_messages;
+DROP PUBLICATION pub;
+DROP TABLE sch1.tbl1;
+DROP SCHEMA sch1 cascade;
+DROP SCHEMA sch2 cascade;
RESET SESSION AUTHORIZATION;
DROP ROLE regress_publication_user, regress_publication_user2;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 40fbcddd20..7bbbb34e2f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -778,6 +778,7 @@ FormData_pg_partitioned_table
FormData_pg_policy
FormData_pg_proc
FormData_pg_publication
+FormData_pg_publication_namespace
FormData_pg_publication_rel
FormData_pg_range
FormData_pg_replication_origin
@@ -834,6 +835,7 @@ Form_pg_partitioned_table
Form_pg_policy
Form_pg_proc
Form_pg_publication
+Form_pg_publication_namespace
Form_pg_publication_rel
Form_pg_range
Form_pg_replication_origin
@@ -2047,8 +2049,11 @@ PsqlSettings
Publication
PublicationActions
PublicationInfo
+PublicationObjSpec
+PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
+PublicationSchemaInfo
PublicationTable
PullFilter
PullFilterOps
--
2.30.2
v47-0002-Add-tap-tests-for-the-schema-publication-feature.patchtext/x-patch; charset=US-ASCII; name=v47-0002-Add-tap-tests-for-the-schema-publication-feature.patchDownload
From 7def9c5eee3cce1ad9e6483f5ebbd7015aa02ddc Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 21 Oct 2021 14:25:24 +0530
Subject: [PATCH v47 2/3] Add tap tests for the schema publication feature of
logical replication
Add tap tests for the schema publication feature of logical replication
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy, Mark Dilger
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
.../t/025_rep_changes_for_schema.pl | 168 ++++++++++++++++++
1 file changed, 168 insertions(+)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..fcdde5ba4c
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,168 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More tests => 14;
+
+# Initialize publisher node
+my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch2.tab2 AS SELECT generate_series(1,10) AS a");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch2");
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch3");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch2.tab2 (a int)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1,sch2");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+ );
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up.
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(20|1|20), 'check rows on subscriber catchup');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data in the subscriber before refresh.
+$node_publisher->safe_psql('postgres', "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check rows on subscriber catchup');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Also wait for initial table sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres', "ALTER TABLE sch1.tab3 SET SCHEMA sch3");
+$node_publisher->safe_psql('postgres', "INSERT INTO sch3.tab3 VALUES(11)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Verify that the subscription relation list is updated after refresh.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')");
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "INSERT INTO sch2.tab1 VALUES(21); ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch2; INSERT INTO sch2.tab1 values(22)");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch2.tab1");
+is($result, qq(21|1|21), 'check rows on subscriber catchup');
+
+# Drop subscription as we don't need it anymore
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_schema");
+
+# Drop publication as we don't need it anymore
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_schema");
+
+# Clean up the schemas on both publisher and subscriber as we don't need them
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_publisher->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch1 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch2 cascade");
+$node_subscriber->safe_psql('postgres', "DROP SCHEMA sch3 cascade");
--
2.30.2
v47-0003-Add-new-pg_publication_objects-view-to-display-T.patchtext/x-patch; charset=US-ASCII; name=v47-0003-Add-new-pg_publication_objects-view-to-display-T.patchDownload
From ba643e06baeb50cb6dfb62d38858d4c46ca358f8 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v47 3/3] Add new "pg_publication_objects" view to display
"TABLE"/"SCHEMA" publication objects
A new "pg_publication_objects" view is added, to display table/schema object
information associated with publications.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy, Mark Dilger
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 19 ++++++++
src/test/regress/expected/rules.out | 15 ++++++
3 files changed, 104 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 00b648a433..1dfb9d1a33 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11332,6 +11337,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..f70348e34f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,25 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..8796f71de2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,21 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM ((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Mon, Oct 25, 2021 at 1:11 PM vignesh C <vignesh21@gmail.com> wrote:
On Mon, Oct 25, 2021 at 10:52 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Oct 22, 2021 at 8:56 PM vignesh C <vignesh21@gmail.com> wrote:
I am getting a compilation error in the latest patch on HEAD. I think
was relying on some variable removed by a recent commit
92316a4582a5714d4e494aaf90360860e7fec37a. While looking at that
compilation error, I observed that we don't need the second and third
parameters in pg_dump.c/getPublicationNamespaces() as those are not
getting used.I have fixed this in the v47 version attached.
Thanks, the first patch in the series "Allow publishing the tables of
schema." looks good to me. Unless there are more
comments/bugs/objections, I am planning to commit it in a day or so.
--
With Regards,
Amit Kapila.
On Mon, Oct 25, 2021 at 3:09 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Oct 25, 2021 at 1:11 PM vignesh C <vignesh21@gmail.com> wrote:
I have fixed this in the v47 version attached.
Thanks, the first patch in the series "Allow publishing the tables of
schema." looks good to me. Unless there are more
comments/bugs/objections, I am planning to commit it in a day or so.
Yesterday, I have pushed the first patch. Feel free to submit the
remaining patches.
--
With Regards,
Amit Kapila.
On Thu, Oct 28, 2021 at 8:12 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Oct 25, 2021 at 3:09 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Oct 25, 2021 at 1:11 PM vignesh C <vignesh21@gmail.com> wrote:
I have fixed this in the v47 version attached.
Thanks, the first patch in the series "Allow publishing the tables of
schema." looks good to me. Unless there are more
comments/bugs/objections, I am planning to commit it in a day or so.Yesterday, I have pushed the first patch. Feel free to submit the
remaining patches.
Thanks for committing the patch, please find the remaining patches attached.
Thanks Hou Zhijie and Greg Nancarrow for sharing a few comments
offline, I have fixed those in the attached patch.
Regards,
Vignesh
Attachments:
v48-0001-Add-tap-tests-for-the-schema-publication-feature.patchtext/x-patch; charset=US-ASCII; name=v48-0001-Add-tap-tests-for-the-schema-publication-feature.patchDownload
From fa0fe25d1143575bdf31489bc8d1c88aa1ea362b Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 21 Oct 2021 14:25:24 +0530
Subject: [PATCH v48 1/2] Add tap tests for the schema publication feature of
logical replication
Add tap tests for the schema publication feature of logical replication
Author: Vignesh C, Tang Haiying
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy, Mark Dilger
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
.../t/025_rep_changes_for_schema.pl | 205 ++++++++++++++++++
1 file changed, 205 insertions(+)
create mode 100644 src/test/subscription/t/025_rep_changes_for_schema.pl
diff --git a/src/test/subscription/t/025_rep_changes_for_schema.pl b/src/test/subscription/t/025_rep_changes_for_schema.pl
new file mode 100644
index 0000000000..8f14da06a7
--- /dev/null
+++ b/src/test/subscription/t/025_rep_changes_for_schema.pl
@@ -0,0 +1,205 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Logical replication tests for schema publications
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More tests => 13;
+
+# Initialize publisher node
+my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Test replication with publications created using FOR ALL TABLES IN SCHEMA
+# option.
+# Create schemas and tables on publisher
+$node_publisher->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE sch1.tab1 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE sch1.tab2 AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE sch1.tab1_parent (a int PRIMARY KEY, b text) PARTITION BY LIST (a)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE public.tab1_child1 PARTITION OF sch1.tab1_parent FOR VALUES IN (1, 2, 3)");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE public.tab1_child2 PARTITION OF sch1.tab1_parent FOR VALUES IN (4, 5, 6)");
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO sch1.tab1_parent values (1),(4)");
+
+# Create schemas and tables on subscriber
+$node_subscriber->safe_psql('postgres', "CREATE SCHEMA sch1");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab1 (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab2 (a int)");
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE sch1.tab1_parent (a int PRIMARY KEY, b text) PARTITION BY LIST (a)");
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE public.tab1_child1 PARTITION OF sch1.tab1_parent FOR VALUES IN (1, 2, 3)");
+$node_subscriber->safe_psql('postgres',
+ "CREATE TABLE public.tab1_child2 PARTITION OF sch1.tab1_parent FOR VALUES IN (4, 5, 6)");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION tap_pub_schema FOR ALL TABLES IN SCHEMA sch1");
+
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION tap_sub_schema CONNECTION '$publisher_connstr' PUBLICATION tap_pub_schema"
+);
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+# Also wait for initial table sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the schema table data is synced up
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab2");
+is($result, qq(10|1|10), 'check rows on subscriber catchup');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT * FROM sch1.tab1_parent order by 1");
+is($result, qq(1|
+4|), 'check rows on subscriber catchup');
+
+# Insert some data into few tables and verify that inserted data is replicated
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO sch1.tab1 VALUES(generate_series(11,20))");
+
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO sch1.tab1_parent values (2),(5)");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(20|1|20), 'check replicated inserts on subscriber');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT * FROM sch1.tab1_parent order by 1");
+is($result, qq(1|
+2|
+4|
+5|), 'check replicated inserts on subscriber');
+
+# Create new table in the publication schema, verify that subscriber does not get
+# the new table data before refresh.
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE sch1.tab3 AS SELECT generate_series(1,10) AS a");
+
+$node_subscriber->safe_psql('postgres', "CREATE TABLE sch1.tab3(a int)");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result =
+ $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM sch1.tab3");
+is($result, qq(0), 'check replicated inserts on subscriber');
+
+# Table data should be reflected after refreshing the publication in
+# subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Wait for sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$node_publisher->safe_psql('postgres', "INSERT INTO sch1.tab3 VALUES(11)");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check rows on subscriber catchup');
+
+# Set the schema of a publication schema table to a non publication schema and
+# verify that inserted data is not reflected by the subscriber.
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE sch1.tab3 SET SCHEMA public");
+$node_publisher->safe_psql('postgres', "INSERT INTO public.tab3 VALUES(12)");
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab3");
+is($result, qq(11|1|11), 'check replicated inserts on subscriber');
+
+# Verify that the subscription relation list is updated after refresh
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')"
+);
+is($result, qq(5),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Ask for data sync
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Wait for sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')"
+);
+is($result, qq(4),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop table from the publication schema, verify that subscriber removes the
+# table entry after refresh.
+$node_publisher->safe_psql('postgres', "DROP TABLE sch1.tab2");
+$node_publisher->wait_for_catchup('tap_sub_schema');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')"
+);
+is($result, qq(4),
+ 'check subscription relation status is not yet dropped on subscriber');
+
+# Table should be removed from pg_subscription_rel after refreshing the
+# publication in subscriber.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_schema REFRESH PUBLICATION");
+
+# Wait for sync to finish
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubid IN (SELECT oid FROM pg_subscription WHERE subname = 'tap_sub_schema')"
+);
+is($result, qq(3),
+ 'check subscription relation status was dropped on subscriber');
+
+# Drop schema from publication, verify that the inserts are not published after
+# dropping the schema from publication. Here 2nd insert should not be
+# published.
+$node_publisher->safe_psql('postgres', "
+ INSERT INTO sch1.tab1 VALUES(21);
+ ALTER PUBLICATION tap_pub_schema DROP ALL TABLES IN SCHEMA sch1;
+ INSERT INTO sch1.tab1 values(22);"
+);
+
+$node_publisher->wait_for_catchup('tap_sub_schema');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*), min(a), max(a) FROM sch1.tab1");
+is($result, qq(21|1|21), 'check replicated inserts on subscriber');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
--
2.30.2
v48-0002-Add-new-pg_publication_objects-view-to-display-T.patchtext/x-patch; charset=US-ASCII; name=v48-0002-Add-new-pg_publication_objects-view-to-display-T.patchDownload
From 9d07432ddff9f2259f9a2b945fcdf5e3aaa03f71 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 31 Aug 2021 18:25:11 +0530
Subject: [PATCH v48 2/2] Add new "pg_publication_objects" view to display
"TABLE"/"SCHEMA" publication objects
A new "pg_publication_objects" view is added, to display table/schema object
information associated with publications.
Author: Vignesh C
Reviewed-by: Amit Kapila, Hou Zhijie, Greg Nancarrow, Masahiko Sawada, Peter Eisentraut, Tom Lane, Peter Smith, Ajin Cherian, Rahila Syed, Bharath Rupireddy, Mark Dilger
Tested-by: Tang Haiying
Discussion: https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ%40mail.gmail.com
---
doc/src/sgml/catalogs.sgml | 70 ++++++++++++++++++++++++++++
src/backend/catalog/system_views.sql | 19 ++++++++
src/test/regress/expected/rules.out | 15 ++++++
3 files changed, 104 insertions(+)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c1d11be73f..45fb28c14a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9503,6 +9503,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>publications and their associated tables</entry>
</row>
+ <row>
+ <entry><link linkend="view-pg-publication-objects"><structname>pg_publication_objects</structname></link></entry>
+ <entry>publications and their associated objects</entry>
+ </row>
+
<row>
<entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
<entry>information about replication origins, including replication progress</entry>
@@ -11333,6 +11338,71 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</sect1>
+<sect1 id="view-pg-publication-objects">
+ <title><structname>pg_publication_objects</structname></title>
+
+ <indexterm zone="view-pg-publication-objects">
+ <primary>pg_publication_objects</primary>
+ </indexterm>
+
+ <para>
+ The view <structname>pg_publication_objects</structname> provides
+ information about the mapping between publications and the objects they
+ contain. Unlike the underlying catalog
+ <link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link>,
+ this view expands publications defined as <literal>FOR TABLE</literal>
+ and <literal>FOR ALL TABLES IN SCHEMA</literal>, so for such publications
+ there will be a row for each eligible object.
+ </para>
+
+ <table>
+ <title><structname>pg_publication_objects</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>pubname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.<structfield>pubname</structfield>)
+ </para>
+ <para>
+ Name of publication
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objname</structfield> <type>name</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>nspname</structfield> or <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>relname</structfield>)
+ </para>
+ <para>
+ Name of schema or Name of table
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>objtype</structfield> <type>name</type>
+ </para>
+ <para>
+ The object type: <literal>schema</literal> or <literal>table</literal>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="view-pg-publication-tables">
<title><structname>pg_publication_tables</structname></title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index eb560955cd..9be1ea8487 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -362,6 +362,25 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL ON pg_statistic_ext_data FROM public;
+CREATE VIEW pg_publication_objects AS
+SELECT
+ P.pubname,
+ N.nspname AS objname,
+ 'schema'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_namespace S ON P.oid = S.pnpubid
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
+UNION
+SELECT
+ P.pubname,
+ quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS objname,
+ 'table'::text AS objtype
+FROM pg_catalog.pg_publication P
+ JOIN pg_catalog.pg_publication_rel R ON P.oid = R.prpubid
+ JOIN pg_catalog.pg_class C ON C.oid = R.prrelid
+ JOIN pg_catalog.pg_namespace N ON N.oid = C.relnamespace
+ORDER BY pubname;
+
CREATE VIEW pg_publication_tables AS
SELECT
P.pubname AS pubname,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..8796f71de2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1451,6 +1451,21 @@ pg_prepared_xacts| SELECT p.transaction,
FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid)
LEFT JOIN pg_authid u ON ((p.ownerid = u.oid)))
LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
+pg_publication_objects| SELECT p.pubname,
+ n.nspname AS objname,
+ 'schema'::text AS objtype
+ FROM ((pg_publication p
+ JOIN pg_publication_namespace s ON ((p.oid = s.pnpubid)))
+ JOIN pg_namespace n ON ((n.oid = s.pnnspid)))
+UNION
+ SELECT p.pubname,
+ ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS objname,
+ 'table'::text AS objtype
+ FROM (((pg_publication p
+ JOIN pg_publication_rel r ON ((p.oid = r.prpubid)))
+ JOIN pg_class c ON ((c.oid = r.prrelid)))
+ JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
+ ORDER BY 1;
pg_publication_tables| SELECT p.pubname,
n.nspname AS schemaname,
c.relname AS tablename
--
2.30.2
On Thu, Oct 28, 2021 at 3:25 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for committing the patch, please find the remaining patches attached.
A few comments on the v48-0002 patch:
(1) The quoting of TABLE/SCHEMA looks a bit odd in the patch comment
(2) src/backend/catalog/system_views.sq
ON should be capitalized in the following line:
+ JOIN pg_catalog.pg_namespace N on N.oid = S.pnnspid
(3) Some basic tests should be added for this in the publication tests.
Regards,
Greg Nancarrow
Fujitsu Australia
On Thu, Oct 28, 2021 at 9:55 AM vignesh C <vignesh21@gmail.com> wrote:
Thanks for committing the patch, please find the remaining patches attached.
Thanks Hou Zhijie and Greg Nancarrow for sharing a few comments
offline, I have fixed those in the attached patch.
Pushed the first test case patch. About
v48-0002-Add-new-pg_publication_objects-view-to-display-T, I think it
doesn't display anything for "for all tables" publication. Instead of
selecting from pg_publication_rel, you can use the existing view
pg_publication_tables to solve that problem.
Having said that, I am not completely sure about the value of this new
view pg_publication_objects which displays all objects of
publications. I see that users might want to see all the objects that
the publication publishes and when we include other objects like
sequences it might be more helpful.
Sawada-San, others, what do you think? Is it really useful to have such a view?
One point to think is if we introduce such a view then how it should
behave for schema objects? Do we need to display only schemas or
additionally all the tables in the schema as well? If you follow the
above suggestion of mine then I think it will display both schemas
published and tables in that schema that will be considered for
publishing.
--
With Regards,
Amit Kapila.
On Friday, October 29, 2021 12:35 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Oct 28, 2021 at 9:55 AM vignesh C <vignesh21@gmail.com> wrote:
Thanks for committing the patch, please find the remaining patches attached.
Thanks Hou Zhijie and Greg Nancarrow for sharing a few comments
offline, I have fixed those in the attached patch.Pushed the first test case patch. About
v48-0002-Add-new-pg_publication_objects-view-to-display-T, I think it
doesn't display anything for "for all tables" publication. Instead of
selecting from pg_publication_rel, you can use the existing view
pg_publication_tables to solve that problem.Having said that, I am not completely sure about the value of this new
view pg_publication_objects which displays all objects of
publications. I see that users might want to see all the objects that
the publication publishes and when we include other objects like
sequences it might be more helpful.Sawada-San, others, what do you think? Is it really useful to have such a view?
One point to think is if we introduce such a view then how it should
behave for schema objects? Do we need to display only schemas or
additionally all the tables in the schema as well? If you follow the
above suggestion of mine then I think it will display both schemas
published and tables in that schema that will be considered for
publishing.
Personally, if I want to see ALL the published objects in a publication, I would use
'\dRp+' command. I think there might not be too much value to have this view.
Regards
Tang
Hi,
On 10/28/21 04:41, Amit Kapila wrote:
On Mon, Oct 25, 2021 at 3:09 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Oct 25, 2021 at 1:11 PM vignesh C <vignesh21@gmail.com> wrote:
I have fixed this in the v47 version attached.
Thanks, the first patch in the series "Allow publishing the tables of
schema." looks good to me. Unless there are more
comments/bugs/objections, I am planning to commit it in a day or so.Yesterday, I have pushed the first patch. Feel free to submit the
remaining patches.
I haven't been following this thread recently, but while rebasing the
sequence decoding patch I noticed this adds
PUBLICATIONOBJ_TABLE, /* Table type */
PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
Shouldn't it be PUBLICATIONOBJ_TABLE_IN_SCHEMA, or why does it use rel
instead of table?
I'm asking because the sequence decoding patch mimics ALTER PUBLICATION
options for sequences, including ALL SEQUENCES IN SCHEMA etc. and this
seems ambiguous. The same issue applies to PUBLICATIONOBJ_CURRSCHEMA,
which does not specify the object type.
I wonder if it'd be better to just separate the schema and object type
specification, instead of mashing it into a single constant. Otherwise
we'll end up with (M x N) combinations, which seems silly.
regards
--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Fri, Oct 29, 2021 at 3:35 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
Sawada-San, others, what do you think? Is it really useful to have such a view?
One point to think is if we introduce such a view then how it should
behave for schema objects? Do we need to display only schemas or
additionally all the tables in the schema as well? If you follow the
above suggestion of mine then I think it will display both schemas
published and tables in that schema that will be considered for
publishing.
I find the proposed view useful for processing the publication
structure and members in SQL, without having to piece the information
together from the other pg_publication_* tables.
Personally I don't think it is necessary to additionally display all
tables in the schema (that information can be retrieved by pg_tables
or information_schema.tables, if needed).
Regards,
Greg Nancarrow
Fujitsu Australia
On Fri, Oct 29, 2021 at 1:35 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Oct 28, 2021 at 9:55 AM vignesh C <vignesh21@gmail.com> wrote:
Thanks for committing the patch, please find the remaining patches attached.
Thanks Hou Zhijie and Greg Nancarrow for sharing a few comments
offline, I have fixed those in the attached patch.Pushed the first test case patch. About
v48-0002-Add-new-pg_publication_objects-view-to-display-T, I think it
doesn't display anything for "for all tables" publication. Instead of
selecting from pg_publication_rel, you can use the existing view
pg_publication_tables to solve that problem.Having said that, I am not completely sure about the value of this new
view pg_publication_objects which displays all objects of
publications. I see that users might want to see all the objects that
the publication publishes and when we include other objects like
sequences it might be more helpful.Sawada-San, others, what do you think? Is it really useful to have such a view?
I haven't followed the discussion on pg_publication_objects view but
what is the primary use case of this view? If it's to list all tables
published in a publication (e.g, "select * from pg_publication_objects
where pubname = 'pub1'), pg_publication_objects view lacks the
information of FOR ALL TABLES publications. And probably we can use
pg_publication_tables instead. On the other hand, if it's to list all
tables published in FOR ALL TABLES IN SCHEMA publications (e.g.,
"select * from pg_publication_object where objtype = 'schema'), the
view doesn't show tables published in such publications.
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Mon, Nov 1, 2021 at 5:07 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
I haven't followed the discussion on pg_publication_objects view but
what is the primary use case of this view? If it's to list all tables
published in a publication (e.g, "select * from pg_publication_objects
where pubname = 'pub1'), pg_publication_objects view lacks the
information of FOR ALL TABLES publications. And probably we can use
pg_publication_tables instead. On the other hand, if it's to list all
tables published in FOR ALL TABLES IN SCHEMA publications (e.g.,
"select * from pg_publication_object where objtype = 'schema'), the
view doesn't show tables published in such publications.
I think that Amit originally suggested to have a view that provides
information about the objects in each publication (like table, tables
in schema, sequence ...).
So it currently is at the granularity level of the objects that are
actually added to the publication (TABLE t, ALL TABLES IN SCHEMA s)
I agree that the view is currently missing ALL TABLES publication
information, but I think it could be easily added.
Also, currently for the "objtype" column, the type "schema" does not
seem specific enough; maybe that should be instead named
"all-tables-in-schema" or similar.
Regards,
Greg Nancarrow
Fujitsu Australia
On Mon, Nov 1, 2021 at 2:48 AM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:
On 10/28/21 04:41, Amit Kapila wrote:
On Mon, Oct 25, 2021 at 3:09 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Oct 25, 2021 at 1:11 PM vignesh C <vignesh21@gmail.com> wrote:
I have fixed this in the v47 version attached.
Thanks, the first patch in the series "Allow publishing the tables of
schema." looks good to me. Unless there are more
comments/bugs/objections, I am planning to commit it in a day or so.Yesterday, I have pushed the first patch. Feel free to submit the
remaining patches.I haven't been following this thread recently, but while rebasing the
sequence decoding patch I noticed this addsPUBLICATIONOBJ_TABLE, /* Table type */
PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */Shouldn't it be PUBLICATIONOBJ_TABLE_IN_SCHEMA, or why does it use rel
instead of table?
Yeah, it should be PUBLICATIONOBJ_TABLE_IN_SCHEMA considering we have
to add other objects like sequence.
I'm asking because the sequence decoding patch mimics ALTER PUBLICATION
options for sequences, including ALL SEQUENCES IN SCHEMA etc. and this
seems ambiguous. The same issue applies to PUBLICATIONOBJ_CURRSCHEMA,
which does not specify the object type.
I think we should name it PUBLICATIONOBJ_TABLE_CURRSCHEMA. Does that make sense?
I wonder if it'd be better to just separate the schema and object type
specification, instead of mashing it into a single constant.
Do you mean to say the syntax on the lines of Create Publication For
Table t1, t2 Schema s1, s2;? If so, then originally the patch had the
syntax on those lines but Tom pointed out that the meaning of such a
syntax can change over a period of time and that can break apps [1]/messages/by-id/155565.1628954580@sss.pgh.pa.us. I
think the current syntax gives a lot of flexibility to users and we
have some precedent for it as well.
[1]: /messages/by-id/155565.1628954580@sss.pgh.pa.us
--
With Regards,
Amit Kapila.
On Mon, Nov 1, 2021 at 2:38 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Mon, Nov 1, 2021 at 5:07 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
I haven't followed the discussion on pg_publication_objects view but
what is the primary use case of this view? If it's to list all tables
published in a publication (e.g, "select * from pg_publication_objects
where pubname = 'pub1'), pg_publication_objects view lacks the
information of FOR ALL TABLES publications. And probably we can use
pg_publication_tables instead. On the other hand, if it's to list all
tables published in FOR ALL TABLES IN SCHEMA publications (e.g.,
"select * from pg_publication_object where objtype = 'schema'), the
view doesn't show tables published in such publications.
Both the problems mentioned can be fixed if we follow the change
suggested by me in one of the emails above [1]/messages/by-id/CAA4eK1+L2-6JQ174sVfE3_K=mjTKJ2A8-z+_pExDHhqdBJvb5Q@mail.gmail.com.
I think that Amit originally suggested to have a view that provides
information about the objects in each publication (like table, tables
in schema, sequence ...).
Right and I think as you also mentioned in your previous email it can
save the effort of users if they want to know all the objects
published via a publication. I am just not sure if it is worth adding
such a view or we leave it to users to find that information via
querying individual views or system tables for objects.
[1]: /messages/by-id/CAA4eK1+L2-6JQ174sVfE3_K=mjTKJ2A8-z+_pExDHhqdBJvb5Q@mail.gmail.com
--
With Regards,
Amit Kapila.
On 11/1/21 11:18, Amit Kapila wrote:
On Mon, Nov 1, 2021 at 2:48 AM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:On 10/28/21 04:41, Amit Kapila wrote:
On Mon, Oct 25, 2021 at 3:09 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Oct 25, 2021 at 1:11 PM vignesh C <vignesh21@gmail.com> wrote:
I have fixed this in the v47 version attached.
Thanks, the first patch in the series "Allow publishing the tables of
schema." looks good to me. Unless there are more
comments/bugs/objections, I am planning to commit it in a day or so.Yesterday, I have pushed the first patch. Feel free to submit the
remaining patches.I haven't been following this thread recently, but while rebasing the
sequence decoding patch I noticed this addsPUBLICATIONOBJ_TABLE, /* Table type */
PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */Shouldn't it be PUBLICATIONOBJ_TABLE_IN_SCHEMA, or why does it use rel
instead of table?Yeah, it should be PUBLICATIONOBJ_TABLE_IN_SCHEMA considering we have
to add other objects like sequence.I'm asking because the sequence decoding patch mimics ALTER PUBLICATION
options for sequences, including ALL SEQUENCES IN SCHEMA etc. and this
seems ambiguous. The same issue applies to PUBLICATIONOBJ_CURRSCHEMA,
which does not specify the object type.I think we should name it PUBLICATIONOBJ_TABLE_CURRSCHEMA. Does that make sense?
I wonder if it'd be better to just separate the schema and object type
specification, instead of mashing it into a single constant.Do you mean to say the syntax on the lines of Create Publication For
Table t1, t2 Schema s1, s2;? If so, then originally the patch had the
syntax on those lines but Tom pointed out that the meaning of such a
syntax can change over a period of time and that can break apps [1]. I
think the current syntax gives a lot of flexibility to users and we
have some precedent for it as well.
No, I'm not talking about the syntax at all - I'm talking about how we
represent it. PUBLICATIONOBJ_TABLE_CURRSCHEMA mixes the object type and
schema in the same constant, so I am wondering if we should just split
that into two pieces - one determining the schema, one determining the
object type. So PublicationObjSpec would have two fields instead of just
pubobjtype.
The advantage would be we wouldn't need a whole lot of new constants for
each object type - adding sequences pretty much means adding
PUBLICATIONOBJ_SEQUENCE
PUBLICATIONOBJ_SEQUENCE_IN_SCHEMA
PUBLICATIONOBJ_SEQUENCE_CURRSCHEMA
and after splitting we'd need just the first one. But maybe it's not
that bad, though. We don't expect all that many object types in
publications, I guess.
regards
--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Mon, Nov 1, 2021 at 7:28 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Nov 1, 2021 at 2:38 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Mon, Nov 1, 2021 at 5:07 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
I haven't followed the discussion on pg_publication_objects view but
what is the primary use case of this view? If it's to list all tables
published in a publication (e.g, "select * from pg_publication_objects
where pubname = 'pub1'), pg_publication_objects view lacks the
information of FOR ALL TABLES publications. And probably we can use
pg_publication_tables instead. On the other hand, if it's to list all
tables published in FOR ALL TABLES IN SCHEMA publications (e.g.,
"select * from pg_publication_object where objtype = 'schema'), the
view doesn't show tables published in such publications.Both the problems mentioned can be fixed if we follow the change
suggested by me in one of the emails above [1].I think that Amit originally suggested to have a view that provides
information about the objects in each publication (like table, tables
in schema, sequence ...).Right and I think as you also mentioned in your previous email it can
save the effort of users if they want to know all the objects
published via a publication.
Thank you for the explanation. Given we already have
pg_publication_tables view, if pg_publication_objects view also shows
all tables published in FOR ALL TABLES publications or FOR ALL TABLES
IN SCHEMA publications, there is essentially not much difference
between pg_publication_tables and pg_publication_objects except for
objtype column. Right? If so it'd be better to have one row for each
FOR ALL TABLES publication and FOR ALL TABLES IN SCHEMA publication
with objtype = 'database' or 'schema' etc, instead of individual
tables.
I am just not sure if it is worth adding
such a view or we leave it to users to find that information via
querying individual views or system tables for objects.
I've not looked at the patch for logical replication of sequences but
the view becomes more useful once we support the new type of
replication object? If so, we can consider this view again after the
patch gets committed.
Regards,
--
Masahiko Sawada
EDB: https://www.enterprisedb.com/
On Tue, Nov 2, 2021 at 6:43 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
I am just not sure if it is worth adding
such a view or we leave it to users to find that information via
querying individual views or system tables for objects.I've not looked at the patch for logical replication of sequences but
the view becomes more useful once we support the new type of
replication object? If so, we can consider this view again after the
patch gets committed.
+1.
--
With Regards,
Amit Kapila.
On Mon, Nov 1, 2021 at 5:52 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:
On 11/1/21 11:18, Amit Kapila wrote:
On Mon, Nov 1, 2021 at 2:48 AM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:I wonder if it'd be better to just separate the schema and object type
specification, instead of mashing it into a single constant.Do you mean to say the syntax on the lines of Create Publication For
Table t1, t2 Schema s1, s2;? If so, then originally the patch had the
syntax on those lines but Tom pointed out that the meaning of such a
syntax can change over a period of time and that can break apps [1]. I
think the current syntax gives a lot of flexibility to users and we
have some precedent for it as well.No, I'm not talking about the syntax at all - I'm talking about how we
represent it. PUBLICATIONOBJ_TABLE_CURRSCHEMA mixes the object type and
schema in the same constant, so I am wondering if we should just split
that into two pieces - one determining the schema, one determining the
object type. So PublicationObjSpec would have two fields instead of just
pubobjtype.The advantage would be we wouldn't need a whole lot of new constants for
each object type - adding sequences pretty much means addingPUBLICATIONOBJ_SEQUENCE
PUBLICATIONOBJ_SEQUENCE_IN_SCHEMA
PUBLICATIONOBJ_SEQUENCE_CURRSCHEMAand after splitting we'd need just the first one.
I see your point but OTOH, I think it will lead to additional checks
in post-processing functions like ObjectsInPublicationToOids() as we
have to always check both object type and schema to make decisions.
But maybe it's not
that bad, though. We don't expect all that many object types in
publications, I guess.
Yeah, that is also true. So maybe at this, we can just rename the few
types as suggested by you and we can look at it later if we anytime
have more number of objects to add.
--
With Regards,
Amit Kapila.
On 11/2/21 11:37 AM, Amit Kapila wrote:
On Mon, Nov 1, 2021 at 5:52 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:On 11/1/21 11:18, Amit Kapila wrote:
On Mon, Nov 1, 2021 at 2:48 AM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:I wonder if it'd be better to just separate the schema and object type
specification, instead of mashing it into a single constant.Do you mean to say the syntax on the lines of Create Publication For
Table t1, t2 Schema s1, s2;? If so, then originally the patch had the
syntax on those lines but Tom pointed out that the meaning of such a
syntax can change over a period of time and that can break apps [1]. I
think the current syntax gives a lot of flexibility to users and we
have some precedent for it as well.No, I'm not talking about the syntax at all - I'm talking about how we
represent it. PUBLICATIONOBJ_TABLE_CURRSCHEMA mixes the object type and
schema in the same constant, so I am wondering if we should just split
that into two pieces - one determining the schema, one determining the
object type. So PublicationObjSpec would have two fields instead of just
pubobjtype.The advantage would be we wouldn't need a whole lot of new constants for
each object type - adding sequences pretty much means addingPUBLICATIONOBJ_SEQUENCE
PUBLICATIONOBJ_SEQUENCE_IN_SCHEMA
PUBLICATIONOBJ_SEQUENCE_CURRSCHEMAand after splitting we'd need just the first one.
I see your point but OTOH, I think it will lead to additional checks
in post-processing functions like ObjectsInPublicationToOids() as we
have to always check both object type and schema to make decisions.
True.
But maybe it's not
that bad, though. We don't expect all that many object types in
publications, I guess.Yeah, that is also true. So maybe at this, we can just rename the few
types as suggested by you and we can look at it later if we anytime
have more number of objects to add.
+1
--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Tue, Nov 2, 2021 at 8:13 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:
Yeah, that is also true. So maybe at this, we can just rename the few
types as suggested by you and we can look at it later if we anytime
have more number of objects to add.+1
Apart from what you have pointed above, we are using
"DO_PUBLICATION_REL_IN_SCHEMA" in pg_dump. I think we should replace
that as well with "DO_PUBLICATION_TABLE_IN_SCHEMA".
--
With Regards,
Amit Kapila.
On Wed, Nov 3, 2021 at 8:30 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Nov 2, 2021 at 8:13 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:Yeah, that is also true. So maybe at this, we can just rename the few
types as suggested by you and we can look at it later if we anytime
have more number of objects to add.+1
Apart from what you have pointed above, we are using
"DO_PUBLICATION_REL_IN_SCHEMA" in pg_dump. I think we should replace
that as well with "DO_PUBLICATION_TABLE_IN_SCHEMA".
Thanks for the comments, the attached patch has the changes for the same.
Regards,
Vignesh
Attachments:
0001-Renamed-enums-that-included-REL-to-TABLE-in-publish-.patchtext/x-patch; charset=US-ASCII; name=0001-Renamed-enums-that-included-REL-to-TABLE-in-publish-.patchDownload
From 533de4b3024e12b033d82995a3b920b51f2a7d64 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Wed, 3 Nov 2021 09:21:22 +0530
Subject: [PATCH] Renamed enums that included REL to TABLE in publish schema
feature.
Renamed PUBLICATIONOBJ_REL_IN_SCHEMA, PUBLICATIONOBJ_CURRSCHEMA,
DO_PUBLICATION_REL_IN_SCHEMA and PRIO_PUBLICATION_REL_IN_SCHEMA
to PUBLICATIONOBJ_TABLE_IN_SCHEMA, PUBLICATIONOBJ_TABLE_IN_CURRSCHEMA,
DO_PUBLICATION_TABLE_IN_SCHEMA and PRIO_PUBLICATION_TABLE_IN_SCHEMA in publish
schema feature, this change is required to avoid confusion with other objects
like sequences which will be available in the future.
---
src/backend/commands/publicationcmds.c | 8 ++++----
src/backend/parser/gram.y | 12 ++++++------
src/bin/pg_dump/pg_dump.c | 6 +++---
src/bin/pg_dump/pg_dump.h | 2 +-
src/bin/pg_dump/pg_dump_sort.c | 6 +++---
src/include/nodes/parsenodes.h | 5 +++--
6 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index d1fff13d2e..c8c45d9426 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -169,13 +169,13 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
case PUBLICATIONOBJ_TABLE:
*rels = lappend(*rels, pubobj->pubtable);
break;
- case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ case PUBLICATIONOBJ_TABLE_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
/* Filter out duplicates if user specifies "sch1, sch1" */
*schemas = list_append_unique_oid(*schemas, schemaid);
break;
- case PUBLICATIONOBJ_CURRSCHEMA:
+ case PUBLICATIONOBJ_TABLE_IN_CURRSCHEMA:
search_path = fetch_search_path(false);
if (search_path == NIL) /* nothing valid in search_path? */
ereport(ERROR,
@@ -214,7 +214,7 @@ CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
if (list_member_oid(schemaidlist, relSchemaId))
{
- if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ if (checkobjtype == PUBLICATIONOBJ_TABLE_IN_SCHEMA)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add schema \"%s\" to publication",
@@ -613,7 +613,7 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
rels = OpenReliIdList(reloids);
CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
- PUBLICATIONOBJ_REL_IN_SCHEMA);
+ PUBLICATIONOBJ_TABLE_IN_SCHEMA);
CloseTableList(rels);
PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d0eb80e69c..8683dc1a37 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9662,14 +9662,14 @@ PublicationObjSpec:
| ALL TABLES IN_P SCHEMA ColId
{
$$ = makeNode(PublicationObjSpec);
- $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE_IN_SCHEMA;
$$->name = $5;
$$->location = @5;
}
| ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
{
$$ = makeNode(PublicationObjSpec);
- $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE_IN_CURRSCHEMA;
$$->location = @5;
}
| ColId
@@ -17351,17 +17351,17 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->name = NULL;
}
}
- else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
- pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_TABLE_IN_CURRSCHEMA)
{
/*
* We can distinguish between the different type of schema
* objects based on whether name and pubtable is set.
*/
if (pubobj->name)
- pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ pubobj->pubobjtype = PUBLICATIONOBJ_TABLE_IN_SCHEMA;
else if (!pubobj->name && !pubobj->pubtable)
- pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ pubobj->pubobjtype = PUBLICATIONOBJ_TABLE_IN_CURRSCHEMA;
else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b9635a95b6..7e98371d25 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4194,7 +4194,7 @@ getPublicationNamespaces(Archive *fout)
continue;
/* OK, make a DumpableObject for this relationship */
- pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_TABLE_IN_SCHEMA;
pubsinfo[j].dobj.catId.tableoid =
atooid(PQgetvalue(res, i, i_tableoid));
pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
@@ -10331,7 +10331,7 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
- case DO_PUBLICATION_REL_IN_SCHEMA:
+ case DO_PUBLICATION_TABLE_IN_SCHEMA:
dumpPublicationNamespace(fout,
(const PublicationSchemaInfo *) dobj);
break;
@@ -18559,7 +18559,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
- case DO_PUBLICATION_REL_IN_SCHEMA:
+ case DO_PUBLICATION_TABLE_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f9af14b793..d1d8608e9c 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,7 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
- DO_PUBLICATION_REL_IN_SCHEMA,
+ DO_PUBLICATION_TABLE_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 9901d9e0ba..410d1790ee 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,7 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
- PRIO_PUBLICATION_REL_IN_SCHEMA,
+ PRIO_PUBLICATION_TABLE_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -136,7 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
- PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
+ PRIO_PUBLICATION_TABLE_IN_SCHEMA, /* DO_PUBLICATION_TABLE_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1479,7 +1479,7 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
- case DO_PUBLICATION_REL_IN_SCHEMA:
+ case DO_PUBLICATION_TABLE_IN_SCHEMA:
snprintf(buf, bufsize,
"PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 49123e28a4..b241e932fb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3649,8 +3649,9 @@ typedef struct PublicationTable
typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* Table type */
- PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
- PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_TABLE_IN_SCHEMA, /* Relations in schema type */
+ PUBLICATIONOBJ_TABLE_IN_CURRSCHEMA, /* Get the first element from
+ * search_path */
PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
} PublicationObjSpecType;
--
2.30.2
On Wed, Nov 3, 2021 12:25 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Nov 3, 2021 at 8:30 AM Amit Kapila <amit.kapila16@gmail.com>
wrote:On Tue, Nov 2, 2021 at 8:13 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:Yeah, that is also true. So maybe at this, we can just rename the
few types as suggested by you and we can look at it later if we
anytime have more number of objects to add.+1
Apart from what you have pointed above, we are using
"DO_PUBLICATION_REL_IN_SCHEMA" in pg_dump. I think we should replace
that as well with "DO_PUBLICATION_TABLE_IN_SCHEMA".Thanks for the comments, the attached patch has the changes for the same.
Thanks for the patch.
I have only one minor comment:
+ PUBLICATIONOBJ_TABLE_IN_SCHEMA, /* Relations in schema type */
I think the ' Relations' in the comments also need to be changed to 'tables'.
The other part of the patch looks good to me.
Best regards,
Hou zj
While you are changing these, maybe also change:
Before: PUBLICATIONOBJ..._CURRSCHEMA
After: PUBLICATIONOBJ..._CUR_SCHEMA
Which seems more readable to me.
------
Kind Regards,
Peter Smith.
Fujitsu Australia
On Wed, Nov 3, 2021 at 11:37 AM Peter Smith <smithpb2250@gmail.com> wrote:
While you are changing these, maybe also change:
Before: PUBLICATIONOBJ..._CURRSCHEMA
After: PUBLICATIONOBJ..._CUR_SCHEMAWhich seems more readable to me.
Thanks for the comment, the attached patch has the changes for the same.
Regards,
Vignesh
Attachments:
v2-0001-Renamed-enums-that-was-specified-as-REL-to-TABLE-.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Renamed-enums-that-was-specified-as-REL-to-TABLE-.patchDownload
From 41bc108cee08f1a7e488b6b801e581e212dcb0a3 Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Wed, 3 Nov 2021 09:21:22 +0530
Subject: [PATCH v2] Renamed enums that was specified as REL to TABLE in
publish schema feature.
Renamed PUBLICATIONOBJ_REL_IN_SCHEMA, PUBLICATIONOBJ_CURRSCHEMA,
DO_PUBLICATION_REL_IN_SCHEMA and PRIO_PUBLICATION_REL_IN_SCHEMA
to PUBLICATIONOBJ_TABLE_IN_SCHEMA, PUBLICATIONOBJ_TABLE_IN_CURRSCHEMA,
DO_PUBLICATION_TABLE_IN_SCHEMA and PRIO_PUBLICATION_TABLE_IN_SCHEMA in publish
schema feature, this change is required to avoid confusion with other objects
like sequences which will be available in the future.
---
src/backend/commands/publicationcmds.c | 8 ++++----
src/backend/parser/gram.y | 12 ++++++------
src/bin/pg_dump/pg_dump.c | 6 +++---
src/bin/pg_dump/pg_dump.h | 2 +-
src/bin/pg_dump/pg_dump_sort.c | 6 +++---
src/include/nodes/parsenodes.h | 5 +++--
6 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index d1fff13d2e..7d4a0e95f6 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -169,13 +169,13 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
case PUBLICATIONOBJ_TABLE:
*rels = lappend(*rels, pubobj->pubtable);
break;
- case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ case PUBLICATIONOBJ_TABLE_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
/* Filter out duplicates if user specifies "sch1, sch1" */
*schemas = list_append_unique_oid(*schemas, schemaid);
break;
- case PUBLICATIONOBJ_CURRSCHEMA:
+ case PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA:
search_path = fetch_search_path(false);
if (search_path == NIL) /* nothing valid in search_path? */
ereport(ERROR,
@@ -214,7 +214,7 @@ CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
if (list_member_oid(schemaidlist, relSchemaId))
{
- if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ if (checkobjtype == PUBLICATIONOBJ_TABLE_IN_SCHEMA)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add schema \"%s\" to publication",
@@ -613,7 +613,7 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
rels = OpenReliIdList(reloids);
CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
- PUBLICATIONOBJ_REL_IN_SCHEMA);
+ PUBLICATIONOBJ_TABLE_IN_SCHEMA);
CloseTableList(rels);
PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d0eb80e69c..beccdfb2fb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9662,14 +9662,14 @@ PublicationObjSpec:
| ALL TABLES IN_P SCHEMA ColId
{
$$ = makeNode(PublicationObjSpec);
- $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE_IN_SCHEMA;
$$->name = $5;
$$->location = @5;
}
| ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
{
$$ = makeNode(PublicationObjSpec);
- $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA;
$$->location = @5;
}
| ColId
@@ -17351,17 +17351,17 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->name = NULL;
}
}
- else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
- pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA)
{
/*
* We can distinguish between the different type of schema
* objects based on whether name and pubtable is set.
*/
if (pubobj->name)
- pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ pubobj->pubobjtype = PUBLICATIONOBJ_TABLE_IN_SCHEMA;
else if (!pubobj->name && !pubobj->pubtable)
- pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ pubobj->pubobjtype = PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA;
else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b9635a95b6..7e98371d25 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4194,7 +4194,7 @@ getPublicationNamespaces(Archive *fout)
continue;
/* OK, make a DumpableObject for this relationship */
- pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_TABLE_IN_SCHEMA;
pubsinfo[j].dobj.catId.tableoid =
atooid(PQgetvalue(res, i, i_tableoid));
pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
@@ -10331,7 +10331,7 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
- case DO_PUBLICATION_REL_IN_SCHEMA:
+ case DO_PUBLICATION_TABLE_IN_SCHEMA:
dumpPublicationNamespace(fout,
(const PublicationSchemaInfo *) dobj);
break;
@@ -18559,7 +18559,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
- case DO_PUBLICATION_REL_IN_SCHEMA:
+ case DO_PUBLICATION_TABLE_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f9af14b793..d1d8608e9c 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,7 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
- DO_PUBLICATION_REL_IN_SCHEMA,
+ DO_PUBLICATION_TABLE_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 9901d9e0ba..410d1790ee 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,7 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
- PRIO_PUBLICATION_REL_IN_SCHEMA,
+ PRIO_PUBLICATION_TABLE_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -136,7 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
- PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
+ PRIO_PUBLICATION_TABLE_IN_SCHEMA, /* DO_PUBLICATION_TABLE_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1479,7 +1479,7 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
- case DO_PUBLICATION_REL_IN_SCHEMA:
+ case DO_PUBLICATION_TABLE_IN_SCHEMA:
snprintf(buf, bufsize,
"PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 49123e28a4..067138e6b5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3649,8 +3649,9 @@ typedef struct PublicationTable
typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* Table type */
- PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
- PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_TABLE_IN_SCHEMA, /* Tables in schema type */
+ PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA, /* Get the first element from
+ * search_path */
PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
} PublicationObjSpecType;
--
2.30.2
On Wed, Nov 3, 2021 at 11:07 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Wed, Nov 3, 2021 12:25 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Nov 3, 2021 at 8:30 AM Amit Kapila <amit.kapila16@gmail.com>
wrote:On Tue, Nov 2, 2021 at 8:13 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:Yeah, that is also true. So maybe at this, we can just rename the
few types as suggested by you and we can look at it later if we
anytime have more number of objects to add.+1
Apart from what you have pointed above, we are using
"DO_PUBLICATION_REL_IN_SCHEMA" in pg_dump. I think we should replace
that as well with "DO_PUBLICATION_TABLE_IN_SCHEMA".Thanks for the comments, the attached patch has the changes for the same.
Thanks for the patch.
I have only one minor comment:+ PUBLICATIONOBJ_TABLE_IN_SCHEMA, /* Relations in schema type */
I think the ' Relations' in the comments also need to be changed to 'tables'.
The other part of the patch looks good to me.
Thanks for the comment, the patch at [1]/messages/by-id/CALDaNm3g4ZaJ8h=16_A+ytbyPUdJPaAz94YzQLqkD=yPu+VPwA@mail.gmail.com has the changes for the same.
[1]: /messages/by-id/CALDaNm3g4ZaJ8h=16_A+ytbyPUdJPaAz94YzQLqkD=yPu+VPwA@mail.gmail.com
Regards,
Vignesh
On Wed, Nov 3, 2021 at 11:55 AM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Nov 3, 2021 at 11:37 AM Peter Smith <smithpb2250@gmail.com> wrote:
While you are changing these, maybe also change:
Before: PUBLICATIONOBJ..._CURRSCHEMA
After: PUBLICATIONOBJ..._CUR_SCHEMAWhich seems more readable to me.
Thanks for the comment, the attached patch has the changes for the same.
LGTM. I'll push this tomorrow unless there are more comments or suggestions.
--
With Regards,
Amit Kapila.
FYI - I found a small problem with one of the new PublicationObjSpec
parser error messages that was introduced by the recent schema
publication commit [1]https://github.com/postgres/postgres/commit/5a2832465fd8984d089e8c44c094e6900d987fcd.
The error message text is assuming that the error originates from
CREATE PUBLICATION, but actually that same error can also come from
ALTER PUBLICATION.
For example,
e.g.1) Here the error came from CREATE PUBLICATION, so the message
text looks OK.
test_pub=# CREATE PUBLICATION p1 FOR t1;
2021-11-04 10:50:17.208 AEDT [738] ERROR: FOR TABLE/FOR ALL TABLES IN
SCHEMA should be specified before the table/schema name(s) at
character 27
2021-11-04 10:50:17.208 AEDT [738] STATEMENT: CREATE PUBLICATION p1 FOR t1;
ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before
the table/schema name(s)
LINE 1: CREATE PUBLICATION p1 FOR t1;
^
~~
e.g.2) Here the error came from ALTER PUBLICATION, so the message text
is not OK because the ALTER syntax [2]https://www.postgresql.org/docs/devel/sql-alterpublication.html does not even have a FOR
keyword.
test_pub=# ALTER PUBLICATION p1 SET t1;
2021-11-04 10:51:53.912 AEDT [738] ERROR: FOR TABLE/FOR ALL TABLES IN
SCHEMA should be specified before the table/schema name(s) at
character 26
2021-11-04 10:51:53.912 AEDT [738] STATEMENT: ALTER PUBLICATION p1 SET t1;
ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before
the table/schema name(s)
LINE 1: ALTER PUBLICATION p1 SET t1;
^
------
[1]: https://github.com/postgres/postgres/commit/5a2832465fd8984d089e8c44c094e6900d987fcd
[2]: https://www.postgresql.org/docs/devel/sql-alterpublication.html
Kind Regards,
Peter Smith.
Fujitsu Australia
On Thurs, Nov 4, 2021 8:12 AM Peter Smith <smithpb2250@gmail.com> wrote:
FYI - I found a small problem with one of the new PublicationObjSpec parser
error messages that was introduced by the recent schema publication commit
[1].The error message text is assuming that the error originates from CREATE
PUBLICATION, but actually that same error can also come from ALTER
PUBLICATION.
e.g.2) Here the error came from ALTER PUBLICATION, so the message text is
not OK because the ALTER syntax [2] does not even have a FOR keyword.test_pub=# ALTER PUBLICATION p1 SET t1;
2021-11-04 10:51:53.912 AEDT [738] ERROR: FOR TABLE/FOR ALL TABLES IN
SCHEMA should be specified before the table/schema name(s) at character 26
2021-11-04 10:51:53.912 AEDT [738] STATEMENT: ALTER PUBLICATION p1 SET
t1;
ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before
the table/schema name(s) LINE 1: ALTER PUBLICATION p1 SET t1;
I think it might be better to report " TABLE/ALL TABLES IN SCHEMA should be specified before ...".
Best regards,
Hou zj
On Thu, Nov 4, 2021 at 11:55 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
On Thurs, Nov 4, 2021 8:12 AM Peter Smith <smithpb2250@gmail.com> wrote:
FYI - I found a small problem with one of the new PublicationObjSpec parser
error messages that was introduced by the recent schema publication commit
[1].The error message text is assuming that the error originates from CREATE
PUBLICATION, but actually that same error can also come from ALTER
PUBLICATION.
e.g.2) Here the error came from ALTER PUBLICATION, so the message text is
not OK because the ALTER syntax [2] does not even have a FOR keyword.test_pub=# ALTER PUBLICATION p1 SET t1;
2021-11-04 10:51:53.912 AEDT [738] ERROR: FOR TABLE/FOR ALL TABLES IN
SCHEMA should be specified before the table/schema name(s) at character 26
2021-11-04 10:51:53.912 AEDT [738] STATEMENT: ALTER PUBLICATION p1 SET
t1;
ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before
the table/schema name(s) LINE 1: ALTER PUBLICATION p1 SET t1;I think it might be better to report " TABLE/ALL TABLES IN SCHEMA should be specified before ...".
+1 - Yes, I also thought the fix should just be some simple/generic
change of the wording like your suggestion (rather than a different
message for both cases).
------
Kind Regards,
Peter Smith.
Fujitsu Australia
On Thu, Nov 4, 2021 at 5:41 AM Peter Smith <smithpb2250@gmail.com> wrote:
FYI - I found a small problem with one of the new PublicationObjSpec
parser error messages that was introduced by the recent schema
publication commit [1].The error message text is assuming that the error originates from
CREATE PUBLICATION, but actually that same error can also come from
ALTER PUBLICATION.For example,
e.g.1) Here the error came from CREATE PUBLICATION, so the message
text looks OK.test_pub=# CREATE PUBLICATION p1 FOR t1;
2021-11-04 10:50:17.208 AEDT [738] ERROR: FOR TABLE/FOR ALL TABLES IN
SCHEMA should be specified before the table/schema name(s) at
character 27
2021-11-04 10:50:17.208 AEDT [738] STATEMENT: CREATE PUBLICATION p1 FOR t1;
ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before
the table/schema name(s)
LINE 1: CREATE PUBLICATION p1 FOR t1;
^
~~e.g.2) Here the error came from ALTER PUBLICATION, so the message text
is not OK because the ALTER syntax [2] does not even have a FOR
keyword.test_pub=# ALTER PUBLICATION p1 SET t1;
2021-11-04 10:51:53.912 AEDT [738] ERROR: FOR TABLE/FOR ALL TABLES IN
SCHEMA should be specified before the table/schema name(s) at
character 26
2021-11-04 10:51:53.912 AEDT [738] STATEMENT: ALTER PUBLICATION p1 SET t1;
ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before
the table/schema name(s)
LINE 1: ALTER PUBLICATION p1 SET t1;
^
Thanks for the comment, I changed the error message to remove the FOR
keyword. The attached patch has the changes for the same.
Regards,
Vignesh
Attachments:
v3-0001-Renamed-few-enums-and-changed-error-message-in-pu.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Renamed-few-enums-and-changed-error-message-in-pu.patchDownload
From 8a36508d18144150d63812778a8c675a75d0f56d Mon Sep 17 00:00:00 2001
From: Vigneshwaran C <vignesh21@gmail.com>
Date: Wed, 3 Nov 2021 09:21:22 +0530
Subject: [PATCH v3] Renamed few enums and changed error message in publish
schema feature.
Renamed PUBLICATIONOBJ_REL_IN_SCHEMA, PUBLICATIONOBJ_CURRSCHEMA,
DO_PUBLICATION_REL_IN_SCHEMA and PRIO_PUBLICATION_REL_IN_SCHEMA
to PUBLICATIONOBJ_TABLE_IN_SCHEMA, PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA,
DO_PUBLICATION_TABLE_IN_SCHEMA and PRIO_PUBLICATION_TABLE_IN_SCHEMA in publish
schema feature, this change is required to avoid confusion with other objects
like sequences which will be available in the future and also changed
the error message to keep it common for both create and alter publication.
---
src/backend/commands/publicationcmds.c | 8 ++++----
src/backend/parser/gram.y | 14 +++++++-------
src/bin/pg_dump/pg_dump.c | 6 +++---
src/bin/pg_dump/pg_dump.h | 2 +-
src/bin/pg_dump/pg_dump_sort.c | 6 +++---
src/include/nodes/parsenodes.h | 5 +++--
src/test/regress/expected/publication.out | 4 ++--
7 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index d1fff13d2e..7d4a0e95f6 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -169,13 +169,13 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
case PUBLICATIONOBJ_TABLE:
*rels = lappend(*rels, pubobj->pubtable);
break;
- case PUBLICATIONOBJ_REL_IN_SCHEMA:
+ case PUBLICATIONOBJ_TABLE_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
/* Filter out duplicates if user specifies "sch1, sch1" */
*schemas = list_append_unique_oid(*schemas, schemaid);
break;
- case PUBLICATIONOBJ_CURRSCHEMA:
+ case PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA:
search_path = fetch_search_path(false);
if (search_path == NIL) /* nothing valid in search_path? */
ereport(ERROR,
@@ -214,7 +214,7 @@ CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
if (list_member_oid(schemaidlist, relSchemaId))
{
- if (checkobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA)
+ if (checkobjtype == PUBLICATIONOBJ_TABLE_IN_SCHEMA)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add schema \"%s\" to publication",
@@ -613,7 +613,7 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
rels = OpenReliIdList(reloids);
CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
- PUBLICATIONOBJ_REL_IN_SCHEMA);
+ PUBLICATIONOBJ_TABLE_IN_SCHEMA);
CloseTableList(rels);
PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d0eb80e69c..a6d0cefa6b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9662,14 +9662,14 @@ PublicationObjSpec:
| ALL TABLES IN_P SCHEMA ColId
{
$$ = makeNode(PublicationObjSpec);
- $$->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE_IN_SCHEMA;
$$->name = $5;
$$->location = @5;
}
| ALL TABLES IN_P SCHEMA CURRENT_SCHEMA
{
$$ = makeNode(PublicationObjSpec);
- $$->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ $$->pubobjtype = PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA;
$$->location = @5;
}
| ColId
@@ -17323,7 +17323,7 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_CONTINUATION)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
+ errmsg("TABLE/ALL TABLES IN SCHEMA should be specified before the table/schema name(s)"),
parser_errposition(pubobj->location));
foreach(cell, pubobjspec_list)
@@ -17351,17 +17351,17 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->name = NULL;
}
}
- else if (pubobj->pubobjtype == PUBLICATIONOBJ_REL_IN_SCHEMA ||
- pubobj->pubobjtype == PUBLICATIONOBJ_CURRSCHEMA)
+ else if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE_IN_SCHEMA ||
+ pubobj->pubobjtype == PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA)
{
/*
* We can distinguish between the different type of schema
* objects based on whether name and pubtable is set.
*/
if (pubobj->name)
- pubobj->pubobjtype = PUBLICATIONOBJ_REL_IN_SCHEMA;
+ pubobj->pubobjtype = PUBLICATIONOBJ_TABLE_IN_SCHEMA;
else if (!pubobj->name && !pubobj->pubtable)
- pubobj->pubobjtype = PUBLICATIONOBJ_CURRSCHEMA;
+ pubobj->pubobjtype = PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA;
else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b9635a95b6..7e98371d25 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4194,7 +4194,7 @@ getPublicationNamespaces(Archive *fout)
continue;
/* OK, make a DumpableObject for this relationship */
- pubsinfo[j].dobj.objType = DO_PUBLICATION_REL_IN_SCHEMA;
+ pubsinfo[j].dobj.objType = DO_PUBLICATION_TABLE_IN_SCHEMA;
pubsinfo[j].dobj.catId.tableoid =
atooid(PQgetvalue(res, i, i_tableoid));
pubsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
@@ -10331,7 +10331,7 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_PUBLICATION_REL:
dumpPublicationTable(fout, (const PublicationRelInfo *) dobj);
break;
- case DO_PUBLICATION_REL_IN_SCHEMA:
+ case DO_PUBLICATION_TABLE_IN_SCHEMA:
dumpPublicationNamespace(fout,
(const PublicationSchemaInfo *) dobj);
break;
@@ -18559,7 +18559,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_POLICY:
case DO_PUBLICATION:
case DO_PUBLICATION_REL:
- case DO_PUBLICATION_REL_IN_SCHEMA:
+ case DO_PUBLICATION_TABLE_IN_SCHEMA:
case DO_SUBSCRIPTION:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f9af14b793..d1d8608e9c 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -81,7 +81,7 @@ typedef enum
DO_POLICY,
DO_PUBLICATION,
DO_PUBLICATION_REL,
- DO_PUBLICATION_REL_IN_SCHEMA,
+ DO_PUBLICATION_TABLE_IN_SCHEMA,
DO_SUBSCRIPTION
} DumpableObjectType;
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 9901d9e0ba..410d1790ee 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -82,7 +82,7 @@ enum dbObjectTypePriorities
PRIO_POLICY,
PRIO_PUBLICATION,
PRIO_PUBLICATION_REL,
- PRIO_PUBLICATION_REL_IN_SCHEMA,
+ PRIO_PUBLICATION_TABLE_IN_SCHEMA,
PRIO_SUBSCRIPTION,
PRIO_DEFAULT_ACL, /* done in ACL pass */
PRIO_EVENT_TRIGGER, /* must be next to last! */
@@ -136,7 +136,7 @@ static const int dbObjectTypePriority[] =
PRIO_POLICY, /* DO_POLICY */
PRIO_PUBLICATION, /* DO_PUBLICATION */
PRIO_PUBLICATION_REL, /* DO_PUBLICATION_REL */
- PRIO_PUBLICATION_REL_IN_SCHEMA, /* DO_PUBLICATION_REL_IN_SCHEMA */
+ PRIO_PUBLICATION_TABLE_IN_SCHEMA, /* DO_PUBLICATION_TABLE_IN_SCHEMA */
PRIO_SUBSCRIPTION /* DO_SUBSCRIPTION */
};
@@ -1479,7 +1479,7 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"PUBLICATION TABLE (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
- case DO_PUBLICATION_REL_IN_SCHEMA:
+ case DO_PUBLICATION_TABLE_IN_SCHEMA:
snprintf(buf, bufsize,
"PUBLICATION TABLES IN SCHEMA (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 49123e28a4..067138e6b5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3649,8 +3649,9 @@ typedef struct PublicationTable
typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* Table type */
- PUBLICATIONOBJ_REL_IN_SCHEMA, /* Relations in schema type */
- PUBLICATIONOBJ_CURRSCHEMA, /* Get the first element from search_path */
+ PUBLICATIONOBJ_TABLE_IN_SCHEMA, /* Tables in schema type */
+ PUBLICATIONOBJ_TABLE_IN_CUR_SCHEMA, /* Get the first element from
+ * search_path */
PUBLICATIONOBJ_CONTINUATION /* Continuation of previous type */
} PublicationObjSpecType;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 0f4fe4db8f..2ff21a7543 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -478,7 +478,7 @@ RESET SEARCH_PATH;
-- check create publication on CURRENT_SCHEMA where TABLE/ALL TABLES in SCHEMA
-- is not specified
CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
-ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+ERROR: TABLE/ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
LINE 1: CREATE PUBLICATION testpub_forschema1 FOR CURRENT_SCHEMA;
^
-- check create publication on CURRENT_SCHEMA along with FOR TABLE
@@ -747,7 +747,7 @@ Tables from schemas:
-- fail specifying table without any of 'FOR ALL TABLES IN SCHEMA' or
--'FOR TABLE' or 'FOR ALL TABLES'
CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
-ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
+ERROR: TABLE/ALL TABLES IN SCHEMA should be specified before the table/schema name(s)
LINE 1: CREATE PUBLICATION testpub_error FOR pub_test2.tbl1;
^
DROP VIEW testpub_view;
--
2.30.2
On Thu, Nov 4, 2021 at 3:24 PM vignesh C <vignesh21@gmail.com> wrote:
On Thu, Nov 4, 2021 at 5:41 AM Peter Smith <smithpb2250@gmail.com> wrote:
FYI - I found a small problem with one of the new PublicationObjSpec
parser error messages that was introduced by the recent schema
publication commit [1].The error message text is assuming that the error originates from
CREATE PUBLICATION, but actually that same error can also come from
ALTER PUBLICATION.For example,
e.g.1) Here the error came from CREATE PUBLICATION, so the message
text looks OK.test_pub=# CREATE PUBLICATION p1 FOR t1;
2021-11-04 10:50:17.208 AEDT [738] ERROR: FOR TABLE/FOR ALL TABLES IN
SCHEMA should be specified before the table/schema name(s) at
character 27
2021-11-04 10:50:17.208 AEDT [738] STATEMENT: CREATE PUBLICATION p1 FOR t1;
ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before
the table/schema name(s)
LINE 1: CREATE PUBLICATION p1 FOR t1;
^
~~e.g.2) Here the error came from ALTER PUBLICATION, so the message text
is not OK because the ALTER syntax [2] does not even have a FOR
keyword.test_pub=# ALTER PUBLICATION p1 SET t1;
2021-11-04 10:51:53.912 AEDT [738] ERROR: FOR TABLE/FOR ALL TABLES IN
SCHEMA should be specified before the table/schema name(s) at
character 26
2021-11-04 10:51:53.912 AEDT [738] STATEMENT: ALTER PUBLICATION p1 SET t1;
ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified before
the table/schema name(s)
LINE 1: ALTER PUBLICATION p1 SET t1;
^Thanks for the comment, I changed the error message to remove the FOR
keyword. The attached patch has the changes for the same.
LGTM.
------
Kind Regards,
Peter Smith.
Fujitsu Australia
On Thu, Nov 4, 2021 at 3:24 PM vignesh C <vignesh21@gmail.com> wrote:
On Thu, Nov 4, 2021 at 5:41 AM Peter Smith <smithpb2250@gmail.com>
wrote:
FYI - I found a small problem with one of the new PublicationObjSpec
parser error messages that was introduced by the recent schema
publication commit [1].The error message text is assuming that the error originates from
CREATE PUBLICATION, but actually that same error can also come from
ALTER PUBLICATION.For example,
e.g.1) Here the error came from CREATE PUBLICATION, so the message
text looks OK.test_pub=# CREATE PUBLICATION p1 FOR t1;
2021-11-04 10:50:17.208 AEDT [738] ERROR: FOR TABLE/FOR ALL TABLES
IN SCHEMA should be specified before the table/schema name(s) at
character 27
2021-11-04 10:50:17.208 AEDT [738] STATEMENT: CREATE PUBLICATION p1
FOR t1;
ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified
before the table/schema name(s) LINE 1: CREATE PUBLICATION p1 FOR
t1;
^
~~e.g.2) Here the error came from ALTER PUBLICATION, so the message
text is not OK because the ALTER syntax [2] does not even have a FOR
keyword.test_pub=# ALTER PUBLICATION p1 SET t1;
2021-11-04 10:51:53.912 AEDT [738] ERROR: FOR TABLE/FOR ALL TABLES
IN SCHEMA should be specified before the table/schema name(s) at
character 26
2021-11-04 10:51:53.912 AEDT [738] STATEMENT: ALTER PUBLICATION p1
SET t1;
ERROR: FOR TABLE/FOR ALL TABLES IN SCHEMA should be specified
before the table/schema name(s) LINE 1: ALTER PUBLICATION p1 SET t1;
^Thanks for the comment, I changed the error message to remove the FOR
keyword. The attached patch has the changes for the same.LGTM.
+1
Best regards,
Hou zj
FYI - I spotted a trivial SQL mistake (?) of the schema publication patch [1]https://github.com/postgres/postgres/commit/5a2832465fd8984d089e8c44c094e6900d987fcd.
See the file describe.c, function describeOneTableDetails.
The new SQL has a 3rd UNION that looks like:
...
"UNION\n"
"SELECT pubname\n"
"FROM pg_catalog.pg_publication p\n"
"WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
"ORDER BY 1;",
oid, oid, oid, oid);
Notice that there is a table alias "p" but it is never used. It seems
to me like it is just an accidental omission. I think it should be
written like -
BEFORE:
"WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
AFTER:
"WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
Doing this will make it consistent with the SQL of the nearby "else"
case which uses the same alias as expected.
------
[1]: https://github.com/postgres/postgres/commit/5a2832465fd8984d089e8c44c094e6900d987fcd
Kind Regards,
Peter Smith.
Fujitsu Australia
On Tue, Nov 9, 2021 at 7:20 AM Peter Smith <smithpb2250@gmail.com> wrote:
FYI - I spotted a trivial SQL mistake (?) of the schema publication patch [1].
See the file describe.c, function describeOneTableDetails.
The new SQL has a 3rd UNION that looks like:...
"UNION\n"
"SELECT pubname\n"
"FROM pg_catalog.pg_publication p\n"
"WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
"ORDER BY 1;",
oid, oid, oid, oid);Notice that there is a table alias "p" but it is never used. It seems
to me like it is just an accidental omission. I think it should be
written like -BEFORE:
"WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
AFTER:
"WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"Doing this will make it consistent with the SQL of the nearby "else"
case which uses the same alias as expected.
The above makes sense to me. So, pushed a fix for this along with
Vignesh's patch to fix other comments related to this work.
--
With Regards,
Amit Kapila.
On Tue, Nov 9, 2021 at 2:51 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Nov 9, 2021 at 7:20 AM Peter Smith <smithpb2250@gmail.com> wrote:
FYI - I spotted a trivial SQL mistake (?) of the schema publication patch [1].
See the file describe.c, function describeOneTableDetails.
The new SQL has a 3rd UNION that looks like:...
"UNION\n"
"SELECT pubname\n"
"FROM pg_catalog.pg_publication p\n"
"WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
"ORDER BY 1;",
oid, oid, oid, oid);Notice that there is a table alias "p" but it is never used. It seems
to me like it is just an accidental omission. I think it should be
written like -BEFORE:
"WHERE puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
AFTER:
"WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"Doing this will make it consistent with the SQL of the nearby "else"
case which uses the same alias as expected.The above makes sense to me. So, pushed a fix for this along with
Vignesh's patch to fix other comments related to this work.
Thanks for committing the patch. I have changed the status of the
Commitfest entry for this patch to Committed. Adding of the view can
be handled once the Sequence patch is committed.
Regards,
Vignesh
I just noticed that this (commit 5a2832465fd8) added a separate catalog
to store schemas which are part of a publication, side-by-side with the
catalog to store relations which are part of a publication. This seems
a strange way to represent publication membership: in order to find out
what objects are members of a publication, you have to scan both
pg_publication_rel and pg_publication_namespace. Wouldn't it make more
sense to have a single catalog for both things, maybe something like
pg_publication_object
oid OID -- unique key (for pg_depend)
prpubid OID -- of pg_publication
prrelid OID -- OID of relation, or 0 if not a relation
prnspid OID -- OID of namespace, or 0 if not a namespace
which seems more natural to me, and pollutes the system less with weird
syscaches, etc.
What do you think?
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
On Fri, Dec 10, 2021 at 6:24 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
I just noticed that this (commit 5a2832465fd8) added a separate catalog
to store schemas which are part of a publication, side-by-side with the
catalog to store relations which are part of a publication. This seems
a strange way to represent publication membership: in order to find out
what objects are members of a publication, you have to scan both
pg_publication_rel and pg_publication_namespace. Wouldn't it make more
sense to have a single catalog for both things, maybe something likepg_publication_object
oid OID -- unique key (for pg_depend)
prpubid OID -- of pg_publication
prrelid OID -- OID of relation, or 0 if not a relation
prnspid OID -- OID of namespace, or 0 if not a namespacewhich seems more natural to me, and pollutes the system less with weird
syscaches, etc.
It will unnecessarily increase the size per row for FOR TABLE
publication both because of the additional column and additional index
(schema_id, pub_id) and vice versa. I think future projects (like
row_filter, column_filter, etc) will make this impact much bigger as
new columns for those won't be required for schema publications.
Basically, as soon as we start to store additional properties for
different objects, storing different objects together would start
becoming more and more worse. This will also in turn increase the scan
cost where we need only schema or rel publication access like where
ever we are using PUBLICATIONNAMESPACEMAP. I think it will increase
the cost of scanning table publications as its corresponding index
will be larger.
--
With Regards,
Amit Kapila.
On Fri, Dec 10, 2021 at 6:24 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
I just noticed that this (commit 5a2832465fd8) added a separate catalog
to store schemas which are part of a publication, side-by-side with the
catalog to store relations which are part of a publication. This seems
a strange way to represent publication membership: in order to find out
what objects are members of a publication, you have to scan both
pg_publication_rel and pg_publication_namespace. Wouldn't it make more
sense to have a single catalog for both things, maybe something likepg_publication_object
oid OID -- unique key (for pg_depend)
prpubid OID -- of pg_publication
prrelid OID -- OID of relation, or 0 if not a relation
prnspid OID -- OID of namespace, or 0 if not a namespacewhich seems more natural to me, and pollutes the system less with weird
syscaches, etc.What do you think?
I felt the existing tables are better normalized than the proposed one.
Regards,
Vignesh
[ reviving one aspect of an old thread ]
vignesh C <vignesh21@gmail.com> writes:
On Mon, Jul 19, 2021 at 9:32 AM tanghy.fnst@fujitsu.com <
tanghy.fnst@fujitsu.com> wrote:I tested your v12 patch and found a problem in the following case.
Step 1:
postgres=# create schema s1;
CREATE SCHEMA
postgres=# create table s1.t1 (a int);
CREATE TABLE
postgres=# create publication pub_t for table s1.t1;
CREATE PUBLICATION
postgres=# create publication pub_s for schema s1;
CREATE PUBLICATIONStep 2:
pg_dump -N s1I dumped and excluded schema s1, pg_dump generated the following SQL:
-------------------------------
ALTER PUBLICATION pub_s ADD SCHEMA s1;I think it was not expected because SQL like "ALTER PUBLICATION pub_t ADD
TABLE s1.t1" was not generated in my case. Thoughts?
Thanks for reporting this issue, this issue is fixed in the v13 patch
I suppose this exchange is what led to this bit in
getPublicationNamespaces:
/*
* We always dump publication namespaces unless the corresponding
* namespace is excluded from the dump.
*/
if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
continue;
I'd like to push back against this on three separate grounds:
1. The behavior this produces is extremely non-obvious and not
adequately explained by the comment, which makes one wonder how
much of it was intended. For example:
* The public schema will be included if listed in FOR ALL TABLES IN,
even though it's not dumped explicitly in the dump, because its dump
mask includes other bits besides DUMP_COMPONENT_DEFINITION. OK, that
was probably intentional, but you wouldn't know it from the comment.
* Schemas created within extensions will be included if listed in FOR
ALL TABLES IN, even though they're not dumped explicitly in the dump.
This seems like a quite accidental by-product of the fact that
checkExtensionMembership will set DUMP_COMPONENT_ACL on extension
member objects, thus making their dump mask not NONE. If this
behavior was intentional, it needs a less-fragile implementation.
* The information_schema will NOT be included, even if it was listed in
FOR ALL TABLES IN. Admittedly, information_schema doesn't normally
contain any tables that'd be useful to publish. But still, this seems
like randomly ignoring the user's intent.
2. The complaint was that if a schema is excluded from the dump
by --exclude-schema, then it should not get included in the
publication either. I think this is at best highly debatable:
arguably it amounts to breaking the publication. It seems
analogous to deciding that if a function is excluded from the
dump, while a view using the function is included, we should
silently adjust the view by removing the output columns or
WHERE clauses that use the function. I'm pretty sure that
nobody would think that was sane. Perhaps there's a case for
excluding the view as a whole, but we don't do that. Besides, the
corresponding behavior would be to exclude the whole publication,
not silently modify its definition.
3. The corresponding test for individual tables listed in
a publication is coded differently:
/*
* Ignore publication membership of tables whose definitions are not
* to be dumped.
*/
if (!(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
continue;
This is considerably easier to understand the effects of than a test
on the whole dump mask: it will list the table if we intend to emit
CREATE TABLE, and not otherwise, regardless of side issues like ACLs.
But why is it different from the code for schemas?
So I think that this was just wrongly thought through. My
preference would be to either delete the above-quoted bit in
getPublicationNamespaces entirely, or make it look like the
test in getPublicationTables. Or maybe we should delete
both of these tests, on the grounds that redefining the
contents of the publication is far outside pg_dump's charter.
BTW, the discussion that caused me to notice this is at [1]/messages/by-id/CAKNkYnwXFBf136=u9UqUxFUVagevLQJ=zGd5BsLhCsatDvQsKQ@mail.gmail.com.
I'd come to the conclusion that doing something on the basis of
"dobj->dump == DUMP_COMPONENT_NONE" is probably an anti-pattern.
regards, tom lane
[1]: /messages/by-id/CAKNkYnwXFBf136=u9UqUxFUVagevLQJ=zGd5BsLhCsatDvQsKQ@mail.gmail.com
On Sat, Dec 14, 2024 at 5:28 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
I suppose this exchange is what led to this bit in
getPublicationNamespaces:/*
* We always dump publication namespaces unless the corresponding
* namespace is excluded from the dump.
*/
if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
continue;I'd like to push back against this on three separate grounds:
1. The behavior this produces is extremely non-obvious and not
adequately explained by the comment, which makes one wonder how
much of it was intended. For example:* The public schema will be included if listed in FOR ALL TABLES IN,
even though it's not dumped explicitly in the dump, because its dump
mask includes other bits besides DUMP_COMPONENT_DEFINITION. OK, that
was probably intentional, but you wouldn't know it from the comment.* Schemas created within extensions will be included if listed in FOR
ALL TABLES IN, even though they're not dumped explicitly in the dump.
This seems like a quite accidental by-product of the fact that
checkExtensionMembership will set DUMP_COMPONENT_ACL on extension
member objects, thus making their dump mask not NONE. If this
behavior was intentional, it needs a less-fragile implementation.* The information_schema will NOT be included, even if it was listed in
FOR ALL TABLES IN. Admittedly, information_schema doesn't normally
contain any tables that'd be useful to publish. But still, this seems
like randomly ignoring the user's intent.2. The complaint was that if a schema is excluded from the dump
by --exclude-schema, then it should not get included in the
publication either. I think this is at best highly debatable:
arguably it amounts to breaking the publication. It seems
analogous to deciding that if a function is excluded from the
dump, while a view using the function is included, we should
silently adjust the view by removing the output columns or
WHERE clauses that use the function. I'm pretty sure that
nobody would think that was sane. Perhaps there's a case for
excluding the view as a whole, but we don't do that. Besides, the
corresponding behavior would be to exclude the whole publication,
not silently modify its definition.3. The corresponding test for individual tables listed in
a publication is coded differently:/*
* Ignore publication membership of tables whose definitions are not
* to be dumped.
*/
if (!(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
continue;This is considerably easier to understand the effects of than a test
on the whole dump mask: it will list the table if we intend to emit
CREATE TABLE, and not otherwise, regardless of side issues like ACLs.
But why is it different from the code for schemas?So I think that this was just wrongly thought through. My
preference would be to either delete the above-quoted bit in
getPublicationNamespaces entirely, or make it look like the
test in getPublicationTables. Or maybe we should delete
both of these tests, on the grounds that redefining the
contents of the publication is far outside pg_dump's charter.
I see a merit in your second suggestion which is to delete these tests
in getPublicationTables() and getPublicationNamespaces() because we
follow similar behavior in the somewhat related subscription case as
well. When a subscription points to a set of publications and we use
'--no-publications' option in pg_dump, it still dumps the
subscription. I tried it with the following test:
Publisher:
postgres=# create schema s1;
CREATE SCHEMA
postgres=# create table t1(c1 int);
CREATE TABLE
postgres=# create publication pub1 for table t1;
CREATE PUBLICATION
postgres=# create publication pub2 for tables in schema s1;
CREATE PUBLICATION
Subscriber:
postgres=# create table t1 (c1 int);
CREATE TABLE
postgres=# create publication pub_on_sub_1 for table t1;
CREATE PUBLICATION
postgres=# create subscription sub1 connection 'dbname = postgres'
publication pub1, pub2;
NOTICE: created replication slot "sub1" on publisher
CREATE SUBSCRIPTION
Now when I performed the dump with '--no-publications' option on the
subscriber node, it didn't include publications which is expected but
did include a subscription definition pointing to the publications as
defined originally.
--
With Regards,
Amit Kapila.
On Sat, 14 Dec 2024 at 05:27, Tom Lane <tgl@sss.pgh.pa.us> wrote:
[ reviving one aspect of an old thread ]
vignesh C <vignesh21@gmail.com> writes:
On Mon, Jul 19, 2021 at 9:32 AM tanghy.fnst@fujitsu.com <
tanghy.fnst@fujitsu.com> wrote:I tested your v12 patch and found a problem in the following case.
Step 1:
postgres=# create schema s1;
CREATE SCHEMA
postgres=# create table s1.t1 (a int);
CREATE TABLE
postgres=# create publication pub_t for table s1.t1;
CREATE PUBLICATION
postgres=# create publication pub_s for schema s1;
CREATE PUBLICATIONStep 2:
pg_dump -N s1I dumped and excluded schema s1, pg_dump generated the following SQL:
-------------------------------
ALTER PUBLICATION pub_s ADD SCHEMA s1;I think it was not expected because SQL like "ALTER PUBLICATION pub_t ADD
TABLE s1.t1" was not generated in my case. Thoughts?
Thanks for reporting this issue, this issue is fixed in the v13 patch
I suppose this exchange is what led to this bit in
getPublicationNamespaces:/*
* We always dump publication namespaces unless the corresponding
* namespace is excluded from the dump.
*/
if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
continue;I'd like to push back against this on three separate grounds:
1. The behavior this produces is extremely non-obvious and not
adequately explained by the comment, which makes one wonder how
much of it was intended. For example:* The public schema will be included if listed in FOR ALL TABLES IN,
even though it's not dumped explicitly in the dump, because its dump
mask includes other bits besides DUMP_COMPONENT_DEFINITION. OK, that
was probably intentional, but you wouldn't know it from the comment.* Schemas created within extensions will be included if listed in FOR
ALL TABLES IN, even though they're not dumped explicitly in the dump.
This seems like a quite accidental by-product of the fact that
checkExtensionMembership will set DUMP_COMPONENT_ACL on extension
member objects, thus making their dump mask not NONE. If this
behavior was intentional, it needs a less-fragile implementation.* The information_schema will NOT be included, even if it was listed in
FOR ALL TABLES IN. Admittedly, information_schema doesn't normally
contain any tables that'd be useful to publish. But still, this seems
like randomly ignoring the user's intent.2. The complaint was that if a schema is excluded from the dump
by --exclude-schema, then it should not get included in the
publication either. I think this is at best highly debatable:
arguably it amounts to breaking the publication. It seems
analogous to deciding that if a function is excluded from the
dump, while a view using the function is included, we should
silently adjust the view by removing the output columns or
WHERE clauses that use the function. I'm pretty sure that
nobody would think that was sane. Perhaps there's a case for
excluding the view as a whole, but we don't do that. Besides, the
corresponding behavior would be to exclude the whole publication,
not silently modify its definition.3. The corresponding test for individual tables listed in
a publication is coded differently:/*
* Ignore publication membership of tables whose definitions are not
* to be dumped.
*/
if (!(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
continue;This is considerably easier to understand the effects of than a test
on the whole dump mask: it will list the table if we intend to emit
CREATE TABLE, and not otherwise, regardless of side issues like ACLs.
But why is it different from the code for schemas?So I think that this was just wrongly thought through. My
preference would be to either delete the above-quoted bit in
getPublicationNamespaces entirely, or make it look like the
test in getPublicationTables. Or maybe we should delete
both of these tests, on the grounds that redefining the
contents of the publication is far outside pg_dump's charter.
We cannot keep the code identical for getPublicationNamespaces and
getPublicationTables because selectDumpableNamespace performs special
handling for the public schema. Specifically, it unsets
DUMP_COMPONENT_DEFINITION for the public namespace, which prevents the
inclusion of 'TABLES IN SCHEMA public' in the publication. That is the
reason we did not keep the code similar to getPublicationTables.
I prefer the other approach to remove both the checks in
getPublicationTables() and getPublicationNamespaces() which also makes
it consistent with the other case that Amit mentioned at [1]/messages/by-id/CAA4eK1+ZYanA51c9NzKM31AqJSw-j0-edGz91+Vh-nsoKdzKfQ@mail.gmail.com.
[1]: /messages/by-id/CAA4eK1+ZYanA51c9NzKM31AqJSw-j0-edGz91+Vh-nsoKdzKfQ@mail.gmail.com
Regards,
Vignesh
On Mon, 16 Dec 2024 at 12:05, vignesh C <vignesh21@gmail.com> wrote:
I prefer the other approach to remove both the checks in
getPublicationTables() and getPublicationNamespaces() which also makes
it consistent with the other case that Amit mentioned at [1].
If I understand your suggestion correctly I think this will break the
"--exclude-schema" option of pg_dump. That change will dump all
mappings between publications and schemas for publications which are
dumped.
That solves the issue with special schemas, but restore will fail if
some schemas were explicitly excluded. pg_dump will include in the
dump ALTER PUBLICATION <pub> ADD TABLES IN SCHEMA <schema> even for
those schemas which are not created during restore.
I think getPublicationNamespaces() should handle both special schemas
and excluded schemas, which can make the code a bit more complex
though.
--
Kind regards,
Artur
On Mon, 16 Dec 2024 at 17:21, Artur Zakirov <zaartur@gmail.com> wrote:
On Mon, 16 Dec 2024 at 12:05, vignesh C <vignesh21@gmail.com> wrote:
I prefer the other approach to remove both the checks in
getPublicationTables() and getPublicationNamespaces() which also makes
it consistent with the other case that Amit mentioned at [1].If I understand your suggestion correctly I think this will break the
"--exclude-schema" option of pg_dump. That change will dump all
mappings between publications and schemas for publications which are
dumped.That solves the issue with special schemas, but restore will fail if
some schemas were explicitly excluded. pg_dump will include in the
dump ALTER PUBLICATION <pub> ADD TABLES IN SCHEMA <schema> even for
those schemas which are not created during restore.
This is already the case in the existing implementation, so users
should not be surprised by the proposed change.
This can be reproduced with the following steps:
-- Create schema and user defined function in schema sch2
create schema sch2;
CREATE FUNCTION sch2.add1(integer, integer)
RETURNS integer
LANGUAGE sql IMMUTABLE STRICT
AS $_$select $1 + $2;$_$;
-- Create a view which references user defined function of a different schema
create schema sch1;
CREATE TABLE sch1.t1 (c1 integer, c2 integer);
CREATE VIEW sch1.v1 AS SELECT c1 FROM sch1.t1 WHERE (sch2.add1(c1, c2) >= 10);
-- Exclude schema sch2 which has the user defined function while dumping
./pg_dump -d postgres -N sch2
You will notice that the schema sch2 and the user defined function in
schema sch2 will not be dumped.
Regards,
Vignesh
On Mon, 16 Dec 2024 at 11:27, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Sat, Dec 14, 2024 at 5:28 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
I suppose this exchange is what led to this bit in
getPublicationNamespaces:/*
* We always dump publication namespaces unless the corresponding
* namespace is excluded from the dump.
*/
if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
continue;I'd like to push back against this on three separate grounds:
1. The behavior this produces is extremely non-obvious and not
adequately explained by the comment, which makes one wonder how
much of it was intended. For example:* The public schema will be included if listed in FOR ALL TABLES IN,
even though it's not dumped explicitly in the dump, because its dump
mask includes other bits besides DUMP_COMPONENT_DEFINITION. OK, that
was probably intentional, but you wouldn't know it from the comment.* Schemas created within extensions will be included if listed in FOR
ALL TABLES IN, even though they're not dumped explicitly in the dump.
This seems like a quite accidental by-product of the fact that
checkExtensionMembership will set DUMP_COMPONENT_ACL on extension
member objects, thus making their dump mask not NONE. If this
behavior was intentional, it needs a less-fragile implementation.* The information_schema will NOT be included, even if it was listed in
FOR ALL TABLES IN. Admittedly, information_schema doesn't normally
contain any tables that'd be useful to publish. But still, this seems
like randomly ignoring the user's intent.2. The complaint was that if a schema is excluded from the dump
by --exclude-schema, then it should not get included in the
publication either. I think this is at best highly debatable:
arguably it amounts to breaking the publication. It seems
analogous to deciding that if a function is excluded from the
dump, while a view using the function is included, we should
silently adjust the view by removing the output columns or
WHERE clauses that use the function. I'm pretty sure that
nobody would think that was sane. Perhaps there's a case for
excluding the view as a whole, but we don't do that. Besides, the
corresponding behavior would be to exclude the whole publication,
not silently modify its definition.3. The corresponding test for individual tables listed in
a publication is coded differently:/*
* Ignore publication membership of tables whose definitions are not
* to be dumped.
*/
if (!(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
continue;This is considerably easier to understand the effects of than a test
on the whole dump mask: it will list the table if we intend to emit
CREATE TABLE, and not otherwise, regardless of side issues like ACLs.
But why is it different from the code for schemas?So I think that this was just wrongly thought through. My
preference would be to either delete the above-quoted bit in
getPublicationNamespaces entirely, or make it look like the
test in getPublicationTables. Or maybe we should delete
both of these tests, on the grounds that redefining the
contents of the publication is far outside pg_dump's charter.I see a merit in your second suggestion which is to delete these tests
in getPublicationTables() and getPublicationNamespaces() because we
follow similar behavior in the somewhat related subscription case as
well. When a subscription points to a set of publications and we use
'--no-publications' option in pg_dump, it still dumps the
subscription.
Currently, the table and schema publications for the
information_schema are not included in the dump. As a result, the
information_schema contents are not published after restoring the
dump. This issue was pointed out by Tom Lane in [1]/messages/by-id/1270733.1734134272@sss.pgh.pa.us.
Here’s an example to demonstrate the problem:
-- Create table in information_schema
CREATE TABLE information_schema.t1(c1 int);
-- Create a table publication whose table is in information_schema
CREATE PUBLICATION pub1 FOR TABLE information_schema.t1;
-- Create a schema publication on information_schema
CREATE PUBLICATION pub2 FOR TABLES IN SCHEMA information_schema ;
When performing a pg_dump, the following information_schema table and
schema statemetns are not included in the dump, though they should be:
ALTER PUBLICATION pub1 ADD TABLE ONLY information_schema.t1;
ALTER PUBLICATION pub2 ADD TABLES IN SCHEMA information_schema;
The issue arises because we explicitly set the dump bitmask to
DUMP_COMPONENT_NONE for the information_schema schema in
selectDumpableNamespace, which also affects the schema's tables.
Later, the checks in the functions getPublicationNamespaces and
getPublicationTables identify that the bitmask is not set, causing
these publication entries to be skipped.
This issue is fixed by removing the schema and table dump bitmask
check, which ensures that information_schema publications are included
in the dump. Additionally, this patch aligns the behavior with the
following scenarios: a)Subscriptions include publications even when
--no-publication is specified, as Amit pointed out in [2]/messages/by-id/CAA4eK1+ZYanA51c9NzKM31AqJSw-j0-edGz91+Vh-nsoKdzKfQ@mail.gmail.com. b) Views
include the dump of a user-defined function, which is not dumped by
default, as mentioned in [3]/messages/by-id/CALDaNm1ZHfZ9ET9fJxhLWCcSr0-hhi3R_sEupoLPzAWRLngujw@mail.gmail.com.
The attached patch has the changes for the same.
[1]: /messages/by-id/1270733.1734134272@sss.pgh.pa.us
[2]: /messages/by-id/CAA4eK1+ZYanA51c9NzKM31AqJSw-j0-edGz91+Vh-nsoKdzKfQ@mail.gmail.com
[3]: /messages/by-id/CALDaNm1ZHfZ9ET9fJxhLWCcSr0-hhi3R_sEupoLPzAWRLngujw@mail.gmail.com
Regards,
Vignesh
Attachments:
v1-0001-Include-information_schema-publications-and-exclu.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Include-information_schema-publications-and-exclu.patchDownload
From 0074d6d6bb4915fb1d7f1454f201725abf2d3ec4 Mon Sep 17 00:00:00 2001
From: Vignesh <vignesh21@gmail.com>
Date: Wed, 18 Dec 2024 12:43:28 +0530
Subject: [PATCH v1] Include information_schema publications and excluded
tables/schemas in dump
Previously, information_schema schema and table publications were excluded
from the dump, which led to their contents not being replicated after restoring
the dump. This issue occurred because the information_schema schema was set with
the DUMP_COMPONENT_NONE bitmask. The problem has been addressed by removing the
dump bitmask check for schemas and tables, ensuring that the corresponding
publications are now correctly included in the dump.
Additionally, this fix improves consistency by ensuring that table and schema
publications are included even when table and schema are excluded, similar to
the handling of dependent objects in other cases: a) Subscriptions that include
publications even when the --no-publication flag is used. b) The inclusion of
user-defined functions in views, even if those functions are excluded from the
dump.
---
src/bin/pg_dump/pg_dump.c | 14 --------------
src/bin/pg_dump/t/002_pg_dump.pl | 17 -----------------
2 files changed, 31 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d34b8ed4bb..39b76815d7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4534,13 +4534,6 @@ getPublicationNamespaces(Archive *fout)
if (nspinfo == NULL)
continue;
- /*
- * We always dump publication namespaces unless the corresponding
- * namespace is excluded from the dump.
- */
- if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
- continue;
-
/* OK, make a DumpableObject for this relationship */
pubsinfo[j].dobj.objType = DO_PUBLICATION_TABLE_IN_SCHEMA;
pubsinfo[j].dobj.catId.tableoid =
@@ -4640,13 +4633,6 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
if (tbinfo == NULL)
continue;
- /*
- * Ignore publication membership of tables whose definitions are not
- * to be dumped.
- */
- if (!(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
- continue;
-
/* OK, make a DumpableObject for this relationship */
pubrinfo[j].dobj.objType = DO_PUBLICATION_REL;
pubrinfo[j].dobj.catId.tableoid =
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index aa1564cd45..fd5d3daa03 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3037,10 +3037,6 @@ my %tests = (
\QALTER PUBLICATION pub1 ADD TABLE ONLY dump_test.test_table;\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => {
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- },
},
'ALTER PUBLICATION pub1 ADD TABLE test_second_table' => {
@@ -3051,7 +3047,6 @@ my %tests = (
\QALTER PUBLICATION pub1 ADD TABLE ONLY dump_test.test_second_table;\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => { exclude_dump_test_schema => 1, },
},
'ALTER PUBLICATION pub1 ADD TABLE test_sixth_table (col3, col2)' => {
@@ -3062,7 +3057,6 @@ my %tests = (
\QALTER PUBLICATION pub1 ADD TABLE ONLY dump_test.test_sixth_table (col2, col3);\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => { exclude_dump_test_schema => 1, },
},
'ALTER PUBLICATION pub1 ADD TABLE test_seventh_table (col3, col2) WHERE (col1 = 1)'
@@ -3074,7 +3068,6 @@ my %tests = (
\QALTER PUBLICATION pub1 ADD TABLE ONLY dump_test.test_seventh_table (col2, col3) WHERE ((col1 = 1));\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => { exclude_dump_test_schema => 1, },
},
'ALTER PUBLICATION pub3 ADD TABLES IN SCHEMA dump_test' => {
@@ -3085,7 +3078,6 @@ my %tests = (
\QALTER PUBLICATION pub3 ADD TABLES IN SCHEMA dump_test;\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => { exclude_dump_test_schema => 1, },
},
'ALTER PUBLICATION pub3 ADD TABLES IN SCHEMA public' => {
@@ -3105,10 +3097,6 @@ my %tests = (
\QALTER PUBLICATION pub3 ADD TABLE ONLY dump_test.test_table;\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => {
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- },
},
'ALTER PUBLICATION pub4 ADD TABLE test_table WHERE (col1 > 0);' => {
@@ -3119,10 +3107,6 @@ my %tests = (
\QALTER PUBLICATION pub4 ADD TABLE ONLY dump_test.test_table WHERE ((col1 > 0));\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => {
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- },
},
'ALTER PUBLICATION pub4 ADD TABLE test_second_table WHERE (col2 = \'test\');'
@@ -3134,7 +3118,6 @@ my %tests = (
\QALTER PUBLICATION pub4 ADD TABLE ONLY dump_test.test_second_table WHERE ((col2 = 'test'::text));\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => { exclude_dump_test_schema => 1, },
},
'CREATE SCHEMA public' => {
--
2.43.0
On Tue, 17 Dec 2024 at 10:43, vignesh C <vignesh21@gmail.com> wrote:
If I understand your suggestion correctly I think this will break the
"--exclude-schema" option of pg_dump. That change will dump all
mappings between publications and schemas for publications which are
dumped.That solves the issue with special schemas, but restore will fail if
some schemas were explicitly excluded. pg_dump will include in the
dump ALTER PUBLICATION <pub> ADD TABLES IN SCHEMA <schema> even for
those schemas which are not created during restore.This is already the case in the existing implementation, so users
should not be surprised by the proposed change.
Currently the behavior isn't the same as the proposed change.
Sorry, I might have been not clear when I described what might be
wrong with this. Here is the example with the proposed patch [1].
Create necessary objects to test:
create schema nsp;
create publication pub for tables in schema nsp;
If you run pg_dump excluding the schema "nsp":
pg_dump -d postgres -U postgres -f backup --exclude-schema=nsp
In the resulting file "backup" you will have:
...
ALTER PUBLICATION pub ADD TABLES IN SCHEMA nsp;
...
which you won't have on the current master. And I think this is not
what users might expect and it can break some of the scenarios because
during restore they will have an error:
ERROR: schema "nsp" does not exist
1. /messages/by-id/CALDaNm1TQqBC5ZP5BsNf2LKVu1kEJNJn2spFwbAtyLn1FoAFGQ@mail.gmail.com
--
Kind regards,
Artur
On Wed, 18 Dec 2024 at 16:34, Artur Zakirov <zaartur@gmail.com> wrote:
On Tue, 17 Dec 2024 at 10:43, vignesh C <vignesh21@gmail.com> wrote:
If I understand your suggestion correctly I think this will break the
"--exclude-schema" option of pg_dump. That change will dump all
mappings between publications and schemas for publications which are
dumped.That solves the issue with special schemas, but restore will fail if
some schemas were explicitly excluded. pg_dump will include in the
dump ALTER PUBLICATION <pub> ADD TABLES IN SCHEMA <schema> even for
those schemas which are not created during restore.This is already the case in the existing implementation, so users
should not be surprised by the proposed change.Currently the behavior isn't the same as the proposed change.
Sorry, I might have been not clear when I described what might be
wrong with this. Here is the example with the proposed patch [1].Create necessary objects to test:
create schema nsp;
create publication pub for tables in schema nsp;If you run pg_dump excluding the schema "nsp":
pg_dump -d postgres -U postgres -f backup --exclude-schema=nsp
In the resulting file "backup" you will have:
...
ALTER PUBLICATION pub ADD TABLES IN SCHEMA nsp;
...which you won't have on the current master. And I think this is not
what users might expect and it can break some of the scenarios because
during restore they will have an error:ERROR: schema "nsp" does not exist
Yes, this is done intentionally in the proposed patch to keep it
consistent with other scenarios in HEAD.
For example, consider the following case:
-- Create schema and user defined function in schema sch2
create schema sch2;
CREATE FUNCTION sch2.add1(integer, integer)
RETURNS integer
LANGUAGE sql IMMUTABLE STRICT
AS $_$select $1 + $2;$_$;
-- Create a view which references user defined function of a different schema
create schema sch1;
CREATE TABLE sch1.t1 (c1 integer, c2 integer);
CREATE VIEW sch1.v1 AS SELECT c1 FROM sch1.t1 WHERE (sch2.add1(c1, c2) >= 10);
-- Exclude schema sch2 which has the user defined function while dumping
./pg_dump -d postgres -Fc -f dump1 -N sch2
The dump file has the reference to sch2.add1 even though sch2 schema
was excluded, dump will not have the user defined functions defined in
schema sch2:
CREATE VIEW sch1.v1 AS
SELECT c1
FROM sch1.t1
WHERE (sch2.add1(c1, c2) >= 10);
Restore using the above dump that was generated will fail with the below error:
./pg_restore -d test1 dump1
pg_restore: error: could not execute query: ERROR: schema "sch2" does not exist
LINE 4: WHERE (sch2.add1(c1, c2) >= 10);
^
Command was: CREATE VIEW sch1.v1 AS
SELECT c1
FROM sch1.t1
WHERE (sch2.add1(c1, c2) >= 10);
The proposed patch is in similar lines.
Regards,
Vignesh
On Thu, Dec 19, 2024 at 12:02 AM vignesh C <vignesh21@gmail.com> wrote:
On Wed, 18 Dec 2024 at 16:34, Artur Zakirov <zaartur@gmail.com> wrote:
On Tue, 17 Dec 2024 at 10:43, vignesh C <vignesh21@gmail.com> wrote:
If I understand your suggestion correctly I think this will break the
"--exclude-schema" option of pg_dump. That change will dump all
mappings between publications and schemas for publications which are
dumped.That solves the issue with special schemas, but restore will fail if
some schemas were explicitly excluded. pg_dump will include in the
dump ALTER PUBLICATION <pub> ADD TABLES IN SCHEMA <schema> even for
those schemas which are not created during restore.This is already the case in the existing implementation, so users
should not be surprised by the proposed change.Currently the behavior isn't the same as the proposed change.
Sorry, I might have been not clear when I described what might be
wrong with this. Here is the example with the proposed patch [1].Create necessary objects to test:
create schema nsp;
create publication pub for tables in schema nsp;If you run pg_dump excluding the schema "nsp":
pg_dump -d postgres -U postgres -f backup --exclude-schema=nsp
In the resulting file "backup" you will have:
...
ALTER PUBLICATION pub ADD TABLES IN SCHEMA nsp;
...which you won't have on the current master. And I think this is not
what users might expect and it can break some of the scenarios because
during restore they will have an error:ERROR: schema "nsp" does not exist
Yes, this is done intentionally in the proposed patch to keep it
consistent with other scenarios in HEAD.
For example, consider the following case:
-- Create schema and user defined function in schema sch2
create schema sch2;
CREATE FUNCTION sch2.add1(integer, integer)
RETURNS integer
LANGUAGE sql IMMUTABLE STRICT
AS $_$select $1 + $2;$_$;-- Create a view which references user defined function of a different schema
create schema sch1;
CREATE TABLE sch1.t1 (c1 integer, c2 integer);
CREATE VIEW sch1.v1 AS SELECT c1 FROM sch1.t1 WHERE (sch2.add1(c1, c2) >= 10);-- Exclude schema sch2 which has the user defined function while dumping
./pg_dump -d postgres -Fc -f dump1 -N sch2The dump file has the reference to sch2.add1 even though sch2 schema
was excluded, dump will not have the user defined functions defined in
schema sch2:
CREATE VIEW sch1.v1 AS
SELECT c1
FROM sch1.t1
WHERE (sch2.add1(c1, c2) >= 10);Restore using the above dump that was generated will fail with the below error:
./pg_restore -d test1 dump1
pg_restore: error: could not execute query: ERROR: schema "sch2" does not exist
LINE 4: WHERE (sch2.add1(c1, c2) >= 10);
^
Command was: CREATE VIEW sch1.v1 AS
SELECT c1
FROM sch1.t1
WHERE (sch2.add1(c1, c2) >= 10);The proposed patch is in similar lines.
I agree with the proposed patch and this behavior. It is on the lines
of what Tom proposed as one of the ways to address the issue raised in
his initial email. Also, it follows the existing behavior in cases
like view<->function dependency as shown by you in this email, and to
some extent subscription<->publication dependency as shown in email
[1]: /messages/by-id/CAA4eK1+ZYanA51c9NzKM31AqJSw-j0-edGz91+Vh-nsoKdzKfQ@mail.gmail.com -- With Regards, Amit Kapila.
Let's wait till the beginning of the next CF to see if there are other
suggestions or any arguments against this proposed change.
[1]: /messages/by-id/CAA4eK1+ZYanA51c9NzKM31AqJSw-j0-edGz91+Vh-nsoKdzKfQ@mail.gmail.com -- With Regards, Amit Kapila.
--
With Regards,
Amit Kapila.
On Fri, Dec 20, 2024 at 11:34 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
I agree with the proposed patch and this behavior. It is on the lines
of what Tom proposed as one of the ways to address the issue raised in
his initial email. Also, it follows the existing behavior in cases
like view<->function dependency as shown by you in this email, and to
some extent subscription<->publication dependency as shown in email
[1].Let's wait till the beginning of the next CF to see if there are other
suggestions or any arguments against this proposed change.
I am planning to push Vignesh's proposed patch in the email (1) in a
day or two unless there are objections or any other comments on the
patch.
(1) - /messages/by-id/CALDaNm1TQqBC5ZP5BsNf2LKVu1kEJNJn2spFwbAtyLn1FoAFGQ@mail.gmail.com
--
With Regards,
Amit Kapila.
On Wed, Dec 18, 2024 at 12:51 PM vignesh C <vignesh21@gmail.com> wrote:
The attached patch has the changes for the same.
@@ -3037,10 +3037,6 @@ my %tests = (
\QALTER PUBLICATION pub1 ADD TABLE ONLY dump_test.test_table;\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => {
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- },
Shall we change the above 'unlike' to 'like' instead of removing it
with an additional comment as to why we expect a different behavior
here?
--
With Regards,
Amit Kapila.
On Wed, 19 Feb 2025 at 15:57, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Dec 18, 2024 at 12:51 PM vignesh C <vignesh21@gmail.com> wrote:
The attached patch has the changes for the same.
@@ -3037,10 +3037,6 @@ my %tests = ( \QALTER PUBLICATION pub1 ADD TABLE ONLY dump_test.test_table;\E /xm, like => { %full_runs, section_post_data => 1, }, - unlike => { - exclude_dump_test_schema => 1, - exclude_test_table => 1, - },Shall we change the above 'unlike' to 'like' instead of removing it
with an additional comment as to why we expect a different behavior
here?
The updated v2 version patch has the changes for the same.
Regards,
Vignesh
Attachments:
v2-0001-Include-information_schema-publications-and-exclu.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Include-information_schema-publications-and-exclu.patchDownload
From 72e22deb7ae273825206403feed670e931ddddf3 Mon Sep 17 00:00:00 2001
From: Vignesh <vignesh21@gmail.com>
Date: Wed, 18 Dec 2024 12:43:28 +0530
Subject: [PATCH v2] Include information_schema publications and excluded
tables/schemas in dump
Previously, information_schema schema and table publications were excluded
from the dump, which led to their contents not being replicated after restoring
the dump. This issue occurred because the information_schema schema was set with
the DUMP_COMPONENT_NONE bitmask. The problem has been addressed by removing the
dump bitmask check for schemas and tables, ensuring that the corresponding
publications are now correctly included in the dump.
Additionally, this fix improves consistency by ensuring that table and schema
publications are included even when table and schema are excluded, similar to
the handling of dependent objects in other cases: a) Subscriptions that include
publications even when the --no-publication flag is used. b) The inclusion of
user-defined functions in views, even if those functions are excluded from the
dump.
---
src/bin/pg_dump/pg_dump.c | 14 --------------
src/bin/pg_dump/t/002_pg_dump.pl | 21 ++++++---------------
2 files changed, 6 insertions(+), 29 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 30dfda8c3f..6370bb711c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4536,13 +4536,6 @@ getPublicationNamespaces(Archive *fout)
if (nspinfo == NULL)
continue;
- /*
- * We always dump publication namespaces unless the corresponding
- * namespace is excluded from the dump.
- */
- if (nspinfo->dobj.dump == DUMP_COMPONENT_NONE)
- continue;
-
/* OK, make a DumpableObject for this relationship */
pubsinfo[j].dobj.objType = DO_PUBLICATION_TABLE_IN_SCHEMA;
pubsinfo[j].dobj.catId.tableoid =
@@ -4642,13 +4635,6 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
if (tbinfo == NULL)
continue;
- /*
- * Ignore publication membership of tables whose definitions are not
- * to be dumped.
- */
- if (!(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
- continue;
-
/* OK, make a DumpableObject for this relationship */
pubrinfo[j].dobj.objType = DO_PUBLICATION_REL;
pubrinfo[j].dobj.catId.tableoid =
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index bc5d9222a2..92461acf6f 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3094,6 +3094,9 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+
+ # Regardless of whether the table or schema is excluded, publications must
+ # still be dumped, as excluded objects do not apply to publications.
'ALTER PUBLICATION pub1 ADD TABLE test_table' => {
create_order => 51,
create_sql =>
@@ -3101,10 +3104,11 @@ my %tests = (
regexp => qr/^
\QALTER PUBLICATION pub1 ADD TABLE ONLY dump_test.test_table;\E
/xm,
- like => { %full_runs, section_post_data => 1, },
- unlike => {
+ like => {
+ %full_runs,
exclude_dump_test_schema => 1,
exclude_test_table => 1,
+ section_post_data => 1,
},
},
@@ -3116,7 +3120,6 @@ my %tests = (
\QALTER PUBLICATION pub1 ADD TABLE ONLY dump_test.test_second_table;\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => { exclude_dump_test_schema => 1, },
},
'ALTER PUBLICATION pub1 ADD TABLE test_sixth_table (col3, col2)' => {
@@ -3127,7 +3130,6 @@ my %tests = (
\QALTER PUBLICATION pub1 ADD TABLE ONLY dump_test.test_sixth_table (col2, col3);\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => { exclude_dump_test_schema => 1, },
},
'ALTER PUBLICATION pub1 ADD TABLE test_seventh_table (col3, col2) WHERE (col1 = 1)'
@@ -3139,7 +3141,6 @@ my %tests = (
\QALTER PUBLICATION pub1 ADD TABLE ONLY dump_test.test_seventh_table (col2, col3) WHERE ((col1 = 1));\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => { exclude_dump_test_schema => 1, },
},
'ALTER PUBLICATION pub3 ADD TABLES IN SCHEMA dump_test' => {
@@ -3150,7 +3151,6 @@ my %tests = (
\QALTER PUBLICATION pub3 ADD TABLES IN SCHEMA dump_test;\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => { exclude_dump_test_schema => 1, },
},
'ALTER PUBLICATION pub3 ADD TABLES IN SCHEMA public' => {
@@ -3170,10 +3170,6 @@ my %tests = (
\QALTER PUBLICATION pub3 ADD TABLE ONLY dump_test.test_table;\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => {
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- },
},
'ALTER PUBLICATION pub4 ADD TABLE test_table WHERE (col1 > 0);' => {
@@ -3184,10 +3180,6 @@ my %tests = (
\QALTER PUBLICATION pub4 ADD TABLE ONLY dump_test.test_table WHERE ((col1 > 0));\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => {
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- },
},
'ALTER PUBLICATION pub4 ADD TABLE test_second_table WHERE (col2 = \'test\');'
@@ -3199,7 +3191,6 @@ my %tests = (
\QALTER PUBLICATION pub4 ADD TABLE ONLY dump_test.test_second_table WHERE ((col2 = 'test'::text));\E
/xm,
like => { %full_runs, section_post_data => 1, },
- unlike => { exclude_dump_test_schema => 1, },
},
'CREATE SCHEMA public' => {
--
2.43.0